+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
+Mon Jul 27 09:18:13 BST 1998 Tony Gale <gale@gtk.org>
+
+ * docs/gtk_tut.sgml: GtkTree section from
+ David Huggins-Daines <bn711@freenet.carleton.ca>,
+ add a GtkText widget example
+ * examples/text/* example/tree/* : new examples for the
+ GtkTree and GtkText widgets
+
Mon Jul 27 00:46:21 CDT 1998 Shawn T. Amundson <amundson@gtk.org>
* Released GTK+ 1.1.0
name="<imain@gtk.org>"></tt>,
Tony Gale <tt><htmlurl url="mailto:gale@gtk.org"
name="<gale@gtk.org>"></tt>
-<date>June 24th, 1998
+<date>July 25th, 1998
<!-- ***************************************************************** -->
<sect>Introduction
GTK uses GNU autoconf for
configuration. Once untar'd, type ./configure --help to see a list of options.
+Th GTK source distribution also contains the complete source to all of the
+examples used in this tutorial, along with Makefiles to aid compilation.
+
To begin our introduction to GTK, we'll start with the simplest program
possible. This program will
create a 200x200 pixel window and has no way of exiting except to be
will output the list of libraries for the compiler to link with and
the directories to find them in.
+Note that the type of single quote used in the compile command above
+is significant.
+
The libraries that are usually linked in are:
<itemize>
<item>The GTK library (-lgtk), the widget library, based on top of GDK.
<tscreen><verb>
/* example-start packbox packbox.c */
+#include <stdio.h>
#include "gtk/gtk.h"
void
GtkListItem as well.
<!-- ***************************************************************** -->
-<sect>Menu Widgets
+<sect> Tree Widget<label id="sec_Tree_Widgets">
<!-- ***************************************************************** -->
<p>
-There are two ways to create menus, there's the easy way, and there's the
-hard way. Both have their uses, but you can usually use the menufactory
-(the easy way). The "hard" way is to create all the menus using the calls
-directly. The easy way is to use the gtk_menu_factory calls. This is
-much simpler, but there are advantages and disadvantages to each approach.
-The menufactory is much easier to use, and to add new menus to, although
-writing a few wrapper functions to create menus using the manual method
-could go a long way towards usability. With the menufactory, it is not
-possible to add images or the character '/' to the menus.
+The purpose of tree widgets is to display hierarchically-organized
+data. The GtkTree widget itself is a vertical container for widgets
+of type GtkTreeItem. GtkTree itself is not terribly different from
+GtkList - both are derived directly from GtkContainer, and the
+GtkContainer methods work in the same way on GtkTree widgets as on
+GtkList widgets. The difference is that GtkTree widgets can be nested
+within other GtkTree widgets. We'll see how to do this shortly.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Manual Menu Creation
+The GtkTree widget has its own window, and defaults to a white
+background, as does GtkList. Also, most of the GtkTree methods work
+in the same way as the corresponding GtkList ones. However, GtkTree
+is not derived from GtkList, so you cannot use them interchangeably.
+
+<sect1> Creating a Tree
<p>
-In the true tradition of teaching, we'll show you the hard
-way first. <tt>:)</>
+A GtkTree is created in the usual way, using:
-There are three widgets that go into making a menubar and submenus:
-<itemize>
-<item>a menu item, which is what the user wants to select, e.g. 'Save'
-<item>a menu, which acts as a container for the menu items, and
-<item>a menubar, which is a container for each of the individual menus,
-</itemize>
+<tscreen><verb>
+GtkWidget* gtk_tree_new( void );
+</verb></tscreen>
-This is slightly complicated by the fact that menu item widgets are used
-for two different things. They are both the widets that are packed into
-the menu, and the widget that is packed into the menubar, which,
-when selected, activiates the menu.
+Like the GtkList widget, a GtkTree will simply keep growing as more
+items are added to it, as well as when subtrees are expanded.
+For this reason, they are almost always packed into a
+GtkScrolledWindow. You might want to use gtk_widget_set_usize() on
+the scrolled window to ensure that it is big enough to see the tree's
+items, as the default size for GtkScrolledWindow is quite small.
-Let's look at the functions that are used to create menus and menubars.
-This first function is used to create a new menubar.
+Now that you have a tree, you'll probably want to add some items to
+it. <ref id="sec_Tree_Item_Widget" name="The Tree Item Widget"> below
+explains the gory details of GtkTreeItem. For now, it'll suffice to
+create one, using:
<tscreen><verb>
-GtkWidget *gtk_menu_bar_new( void );
+GtkWidget* gtk_tree_item_new_with_label( gchar *label );
</verb></tscreen>
-This rather self explanatory function creates a new menubar. You use
-gtk_container_add to pack this into a window, or the box_pack functions to
-pack it into a box - the same as buttons.
+You can then add it to the tree using one of the following (see
+<ref id="sec_GtkTree_Functions" name="Functions and Macros">
+below for more options):
<tscreen><verb>
-GtkWidget *gtk_menu_new( void );
+void gtk_tree_append( GtkTree *tree,
+ GtkWidget *tree_item );
+
+void gtk_tree_prepend( GtkTree *tree,
+ GtkWidget *tree_item );
</verb></tscreen>
-This function returns a pointer to a new menu, it is never actually shown
-(with gtk_widget_show), it is just a container for the menu items. Hopefully this will
-become more clear when you look at the example below.
+Note that you must add items to a GtkTree one at a time - there is no
+equivalent to gtk_list_*_items().
-The next two calls are used to create menu items that are packed into
-the menu (and menubar).
+<sect1> Adding a Subtree
+<p>
+A subtree is created like any other GtkTree widget. A subtree is added
+to another tree beneath a tree item, using:
<tscreen><verb>
-GtkWidget *gtk_menu_item_new( void );
+void gtk_tree_item_set_subtree( GtkTreeItem *tree_item,
+ GtkWidget *subtree );
</verb></tscreen>
-and
+You do not need to call gtk_widget_show() on a subtree before or after
+adding it to a GtkTreeItem. However, you <em>must</em> have added the
+GtkTreeItem in question to a parent tree before calling
+gtk_tree_item_set_subtree(). This is because, technically, the parent
+of the subtree is <em>not</em> the GtkTreeItem which "owns" it, but
+rather the GtkTree which holds that GtkTreeItem.
+
+When you add a subtree to a GtkTreeItem, a plus or minus sign appears
+beside it, which the user can click on to "expand" or "collapse" it,
+meaning, to show or hide its subtree. GtkTreeItems are collapsed by
+default. Note that when you collapse a GtkTreeItem, any selected
+items in its subtree remain selected, which may not be what the user
+expects.
+
+<sect1> Handling the Selection List
+<p>
+As with GtkList, the GtkTree type has a <tt>selection</tt> field, and
+it is possible to control the behaviour of the tree (somewhat) by
+setting the selection type using:
<tscreen><verb>
-GtkWidget *gtk_menu_item_new_with_label( const char *label );
+void gtk_tree_set_selection_mode( GtkTree *tree,
+ GtkSelectionMode mode );
</verb></tscreen>
-These calls are used to create the menu items that are to be displayed.
-Remember to differentiate between a "menu" as created with gtk_menu_new
-and a "menu item" as created by the gtk_menu_item_new functions. The
-menu item will be an actual button with an associated action,
-whereas a menu will be a container holding menu items.
+The semantics associated with the various selection modes are
+described in the section on the GtkList widget. As with the GtkList
+widget, the "select_child", "unselect_child" (not really - see <ref
+id="sec_GtkTree_Signals" name="Signals"> below for an explanation),
+and "selection_changed" signals are emitted when list items are
+selected or unselected. However, in order to take advantage of these
+signals, you need to know <em>which</em> GtkTree widget they will be
+emitted by, and where to find the list of selected items.
-The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after
-reading about the buttons. One creates a new menu item with a label
-already packed into it, and the other just creates a blank menu item.
+This is a source of potential confusion. The best way to explain this
+is that though all GtkTree widgets are created equal, some are more
+equal than others. All GtkTree widgets have their own X window, and
+can therefore receive events such as mouse clicks (if their
+GtkTreeItems or their children don't catch them first!). However, to
+make GTK_SELECTION_SINGLE and GTK_SELECTION_BROWSE selection types
+behave in a sane manner, the list of selected items is specific to the
+topmost GtkTree widget in a hierarchy, known as the "root tree".
-Once you've created a menu item you have to put it into a menu. This is
-done using the function gtk_menu_append. In order to capture when the item
-is selected by the user, we need to connect to the <tt/activate/ signal in
-the usual way. So, if we wanted to create a standard <tt/File/ menu, with
-the options <tt/Open/, <tt/Save/ and <tt/Quit/ the code would look something like
+Thus, accessing the <tt>selection</tt>field directly in an arbitrary
+GtkTree widget is not a good idea unless you <em>know</em> it's the
+root tree. Instead, use the GTK_TREE_SELECTION (Tree) macro, which
+gives the root tree's selection list as a GList pointer. Of course,
+this list can include items that are not in the subtree in question if
+the selection type is GTK_SELECTION_MULTIPLE.
+
+Finally, the "select_child" (and "unselect_child", in theory) signals
+are emitted by all trees, but the "selection_changed" signal is only
+emitted by the root tree. Consequently, if you want to handle the
+"select_child" signal for a tree and all its subtrees, you will have
+to call gtk_signal_connect() for every subtree.
+
+<sect1> Tree Widget Internals
+<p>
+The GtkTree's struct definition looks like this:
<tscreen><verb>
-file_menu = gtk_menu_new(); /* Don't need to show menus */
+struct _GtkTree
+{
+ GtkContainer container;
-/* Create the menu items */
-open_item = gtk_menu_item_new_with_label("Open");
-save_item = gtk_menu_item_new_with_label("Save");
-quit_item = gtk_menu_item_new_with_label("Quit");
+ GList *children;
+
+ GtkTree* root_tree; /* owner of selection list */
+ GtkWidget* tree_owner;
+ GList *selection;
+ guint level;
+ guint indent_value;
+ guint current_indent;
+ guint selection_mode : 2;
+ guint view_mode : 1;
+ guint view_line : 1;
+};
+</verb></tscreen>
-/* Add them to the menu */
-gtk_menu_append( GTK_MENU(file_menu), open_item);
-gtk_menu_append( GTK_MENU(file_menu), save_item);
-gtk_menu_append( GTK_MENU(file_menu), quit_item);
+The perils associated with accessing the <tt>selection</tt> field
+directly have already been mentioned. The other important fields of
+the struct can also be accessed with handy macros or class functions.
+GTK_TREE_IS_ROOT_TREE (Tree) returns a boolean value which indicates
+whether a tree is the root tree in a GtkTree hierarchy, while
+GTK_TREE_ROOT_TREE (Tree) returns the root tree, an object of type
+GtkTree (so, remember to cast it using GTK_WIDGET (Tree) if you want
+to use one of the gtk_widget_*() functions on it).
-/* Attach the callback functions to the activate signal */
-gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
-gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
+Instead of directly accessing the children field of a GtkTree widget,
+it's probably best to cast it using GTK_CONTAINER (Tree), and pass it
+to the gtk_container_children() function. This creates a duplicate of
+the original list, so it's advisable to free it up using g_list_free()
+after you're done with it, or to iterate on it destructively, like
+this:
-/* We can attach the Quit menu item to our exit function */
-gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
- GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
+<tscreen><verb>
+children = gtk_container_children (GTK_CONTAINER (tree));
+while (children) {
+ do_something_nice (GTK_TREE_ITEM (children->data));
+ children = g_list_remove_link (children, children);
+}
+</verb></tscreen>
-/* We do need to show menu items */
-gtk_widget_show( open_item );
-gtk_widget_show( save_item );
-gtk_widget_show( quit_item );
+The <tt>tree_owner</tt> field is defined only in subtrees, where it
+points to the GtkTreeItem widget which holds the tree in question.
+The <tt>level</tt> field indicates how deeply nested a particular tree
+is; root trees have level 0, and each successive level of subtrees has
+a level one greater than the parent level. This field is set only
+after a GtkTree widget is actually mapped (i.e. drawn on the screen).
+
+<sect2> Signals<label id="sec_GtkTree_Signals">
+<p>
+<tscreen><verb>
+void selection_changed( GtkTree *tree );
</verb></tscreen>
-At this point we have our menu. Now we need to create a menubar and a menu
-item for the <tt/File/ entry, to which we add our menu. The code looks like this
+This signal will be emitted whenever the <tt>selection</tt> field of a
+GtkTree has changed. This happens when a child of the GtkTree is
+selected or deselected.
<tscreen><verb>
-menu_bar = gtk_menu_bar_new();
-gtk_container_add( GTK_CONTAINER(window), menu_bar);
-gtk_widget_show( menu_bar );
+void select_child( GtkTree *tree,
+ GtkWidget *child );
+</verb></tscreen>
-file_item = gtk_menu_item_new_with_label("File");
-gtk_widget_show(file_item);
+This signal is emitted when a child of the GtkTree is about to get
+selected. This happens on calls to gtk_tree_select_item(),
+gtk_tree_select_child(), on <em>all</em> button presses and calls to
+gtk_tree_item_toggle() and gtk_item_toggle(). It may sometimes be
+indirectly triggered on other occasions where children get added to or
+removed from the GtkTree.
+
+<tscreen><verb>
+void unselect_child (GtkTree *tree,
+ GtkWidget *child);
</verb></tscreen>
-Now we need to associate the menu with <tt/file_item/. This is done with the
-function
+This signal is emitted when a child of the GtkTree is about to get
+deselected. As of GTK+ 1.0.4, this seems to only occur on calls to
+gtk_tree_unselect_item() or gtk_tree_unselect_child(), and perhaps on
+other occasions, but <em>not</em> when a button press deselects a
+child, nor on emission of the "toggle" signal by gtk_item_toggle().
-<tscreen>
-void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
- GtkWidget *submenu );
-</tscreen>
+<sect2> Functions and Macros<label id="sec_GtkTree_Functions">
+<p>
+<tscreen><verb>
+guint gtk_tree_get_type( void );
+</verb></tscreen>
-So, our example would continue with
+Returns the `GtkTree' type identifier.
<tscreen><verb>
-gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );
+GtkWidget* gtk_tree_new( void );
</verb></tscreen>
-All that is left to do is to add the menu to the menubar, which is accomplished
-using the function
+Create a new GtkTree object. The new widget is returned as a pointer to a
+GtkWidget object. NULL is returned on failure.
-<tscreen>
-void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
-</tscreen>
+<tscreen><verb>
+void gtk_tree_append( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
-which in our case looks like this:
+Append a tree item to a GtkTree.
<tscreen><verb>
-gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );
+void gtk_tree_prepend( GtkTree *tree,
+ GtkWidget *tree_item );
</verb></tscreen>
-If we wanted the menu right justified on the menubar, such as help menus
-often are, we can use the following function (again on <tt/file_item/
-in the current example) before attaching it to the menubar.
+Prepend a tree item to a GtkTree.
<tscreen><verb>
-void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
+void gtk_tree_insert( GtkTree *tree,
+ GtkWidget *tree_item,
+ gint position );
</verb></tscreen>
-Here is a summary of the steps needed to create a menu bar with menus attached:
+Insert a tree item into a GtkTree at the position in the list
+specified by <tt>position.</tt>
-<itemize>
-<item> Create a new menu using gtk_menu_new()
-<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have
-on your menu. And use gtk_menu_append() to put each of these new items on
-to the menu.
-<item> Create a menu item using gtk_menu_item_new(). This will be the root of
-the menu, the text appearing here will be on the menubar itself.
-<item>Use gtk_menu_item_set_submenu() to attach the menu to the root menu
-item (the one created in the above step).
-<item> Create a new menubar using gtk_menu_bar_new. This step only needs
-to be done once when creating a series of menus on one menu bar.
-<item> Use gtk_menu_bar_append to put the root menu onto the menubar.
-</itemize>
+<tscreen><verb>
+void gtk_tree_remove_items( GtkTree *tree,
+ GList *items );
+</verb></tscreen>
-Creating a popup menu is nearly the same. The difference is that the
-menu is not posted `automatically' by a menubar, but explicitly by calling
-the function gtk_menu_popup() from a button-press event, for example.
-Take these steps:
+Remove a list of items (in the form of a GList *) from a GtkTree.
+Note that removing an item from a tree dereferences (and thus usually)
+destroys it <em>and</em> its subtree, if it has one, <em>and</em> all
+subtrees in that subtree. If you want to remove only one item, you
+can use gtk_container_remove().
-<itemize>
-<item>Create an event handling function. It needs to have the prototype
-<tscreen>
-static gint handler( GtkWidget *widget,
- GdkEvent *event );
-</tscreen>
-and it will use the event to find out where to pop up the menu.
-<item>In the event handler, if the event is a mouse button press, treat
-<tt>event</tt> as a button event (which it is) and use it as
-shown in the sample code to pass information to gtk_menu_popup().
-<item>Bind that event handler to a widget with
-<tscreen>
-gtk_signal_connect_object(GTK_OBJECT(widget), "event",
- GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
-</tscreen>
-where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt>
-is the handling function, and <tt>menu</tt> is a menu created with
-gtk_menu_new(). This can be a menu which is also posted by a menu bar,
-as shown in the sample code.
-</itemize>
+<tscreen><verb>
+void gtk_tree_clear_items( GtkTree *tree,
+ gint start,
+ gint end );
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1>Manual Menu Example
-<p>
-That should about do it. Let's take a look at an example to help clarify.
+Remove the items from position <tt>start</tt> to position <tt>end</tt>
+from a GtkTree. The same warning about dereferencing applies here, as
+gtk_tree_clear_items() simply constructs a list and passes it to
+gtk_tree_remove_items().
<tscreen><verb>
-/* example-start menu menu.c */
+void gtk_tree_select_item( GtkTree *tree,
+ gint item );
+</verb></tscreen>
-#include <gtk/gtk.h>
+Emits the "select_item" signal for the child at position
+<tt>item</tt>, thus selecting the child (unless you unselect it in a
+signal handler...)
-static gint button_press (GtkWidget *, GdkEvent *);
-static void menuitem_response (gchar *);
+<tscreen><verb>
+void gtk_tree_unselect_item( GtkTree *tree,
+ gint item );
+</verb></tscreen>
-int main (int argc, char *argv[])
-{
+Emits the "unselect_item" signal for the child at position
+<tt>item</tt>, thus unselecting the child.
- GtkWidget *window;
- GtkWidget *menu;
- GtkWidget *menu_bar;
- GtkWidget *root_menu;
- GtkWidget *menu_items;
- GtkWidget *vbox;
- GtkWidget *button;
- char buf[128];
- int i;
+<tscreen><verb>
+void gtk_tree_select_child( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
- gtk_init (&argc, &argv);
+Emits the "select_item" signal for the child <tt>tree_item</tt>, thus
+selecting it.
- /* create a new window */
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
- gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
- gtk_signal_connect(GTK_OBJECT (window), "delete_event",
- (GtkSignalFunc) gtk_main_quit, NULL);
+<tscreen><verb>
+void gtk_tree_unselect_child( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
- /* Init the menu-widget, and remember -- never
- * gtk_show_widget() the menu widget!!
- * This is the menu that holds the menu items, the one that
- * will pop up when you click on the "Root Menu" in the app */
- menu = gtk_menu_new();
+Emits the "unselect_item" signal for the child <tt>tree_item</tt>,
+thus unselecting it.
- /* Next we make a little loop that makes three menu-entries for "test-menu".
- * Notice the call to gtk_menu_append. Here we are adding a list of
- * menu items to our menu. Normally, we'd also catch the "clicked"
- * signal on each of the menu items and setup a callback for it,
- * but it's omitted here to save space. */
+<tscreen><verb>
+gint gtk_tree_child_position( GtkTree *tree,
+ GtkWidget *child );
+</verb></tscreen>
- for(i = 0; i < 3; i++)
- {
- /* Copy the names to the buf. */
- sprintf(buf, "Test-undermenu - %d", i);
+Returns the position in the tree of <tt>child</tt>, unless
+<tt>child</tt> is not in the tree, in which case it returns -1.
- /* Create a new menu-item with a name... */
- menu_items = gtk_menu_item_new_with_label(buf);
+<tscreen><verb>
+void gtk_tree_set_selection_mode( GtkTree *tree,
+ GtkSelectionMode mode );
+</verb></tscreen>
- /* ...and add it to the menu. */
- gtk_menu_append(GTK_MENU (menu), menu_items);
+Sets the selection mode, which can be one of GTK_SELECTION_SINGLE (the
+default), GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE, or
+GTK_SELECTION_EXTENDED. This is only defined for root trees, which
+makes sense, since the root tree "owns" the selection. Setting it for
+subtrees has no effect at all; the value is simply ignored.
- /* Do something interesting when the menuitem is selected */
- gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
+<tscreen><verb>
+void gtk_tree_set_view_mode( GtkTree *tree,
+ GtkTreeViewMode mode );
+</verb></tscreen>
- /* Show the widget */
- gtk_widget_show(menu_items);
- }
+Sets the "view mode", which can be either GTK_TREE_VIEW_LINE (the
+default) or GTK_TREE_VIEW_ITEM. The view mode propagates from a tree
+to its subtrees, and can't be set exclusively to a subtree (this is
+not exactly true - see the example code comments).
- /* This is the root menu, and will be the label
- * displayed on the menu bar. There won't be a signal handler attached,
- * as it only pops up the rest of the menu when pressed. */
- root_menu = gtk_menu_item_new_with_label("Root Menu");
+The term "view mode" is rather ambiguous - basically, it controls the
+way the hilight is drawn when one of a tree's children is selected.
+If it's GTK_TREE_VIEW_LINE, the entire GtkTreeItem widget is
+hilighted, while for GTK_TREE_VIEW_ITEM, only the child widget
+(i.e. usually the label) is hilighted.
- gtk_widget_show(root_menu);
+<tscreen><verb>
+void gtk_tree_set_view_lines( GtkTree *tree,
+ guint flag );
+</verb></tscreen>
- /* Now we specify that we want our newly created "menu" to be the menu
- * for the "root menu" */
- gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
+Controls whether connecting lines between tree items are drawn.
+<tt>flag</tt> is either TRUE, in which case they are, or FALSE, in
+which case they aren't.
- /* A vbox to put a menu and a button in: */
- vbox = gtk_vbox_new(FALSE, 0);
- gtk_container_add(GTK_CONTAINER(window), vbox);
- gtk_widget_show(vbox);
+<tscreen><verb>
+GtkTree *GTK_TREE (gpointer obj);
+</verb></tscreen>
- /* Create a menu-bar to hold the menus and add it to our main window */
- menu_bar = gtk_menu_bar_new();
- gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
- gtk_widget_show(menu_bar);
+Cast a generic pointer to `GtkTree *'.
- /* Create a button to which to attach menu as a popup */
- button = gtk_button_new_with_label("press me");
- gtk_signal_connect_object(GTK_OBJECT(button), "event",
- GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
- gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
- gtk_widget_show(button);
+<tscreen><verb>
+GtkTreeClass *GTK_TREE_CLASS (gpointer class);
+</verb></tscreen>
- /* And finally we append the menu-item to the menu-bar -- this is the
- * "root" menu-item I have been raving about =) */
- gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
+Cast a generic pointer to `GtkTreeClass*'.
- /* always display the window as the last step so it all splashes on
- * the screen at once. */
- gtk_widget_show(window);
+<tscreen><verb>
+gint GTK_IS_TREE (gpointer obj);
+</verb></tscreen>
- gtk_main ();
+Determine if a generic pointer refers to a `GtkTree' object.
- return 0;
-}
+<tscreen><verb>
+gint GTK_IS_ROOT_TREE (gpointer obj)
+</verb></tscreen>
-/* Respond to a button-press by posting a menu passed in as widget.
- *
- * Note that the "widget" argument is the menu being posted, NOT
- * the button that was pressed.
- */
+Determine if a generic pointer refers to a `GtkTree' object
+<em>and</em> is a root tree. Though this will accept any pointer, the
+results of passing it a pointer that does not refer to a GtkTree are
+undefined and possibly harmful.
-static gint button_press (GtkWidget *widget, GdkEvent *event)
-{
+<tscreen><verb>
+GtkTree *GTK_TREE_ROOT_TREE (gpointer obj)
+</verb></tscreen>
- if (event->type == GDK_BUTTON_PRESS) {
- GdkEventButton *bevent = (GdkEventButton *) event;
- gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
- bevent->button, bevent->time);
- /* Tell calling code that we have handled this event; the buck
- * stops here. */
- return TRUE;
- }
+Return the root tree of a pointer to a `GtkTree' object. The above
+warning applies.
- /* Tell calling code that we have not handled this event; pass it on. */
- return FALSE;
-}
+<tscreen><verb>
+GList *GTK_TREE_SELECTION( gpointer obj)
+</verb></tscreen>
+Return the selection list of the root tree of a `GtkTree' object. The
+above warning applies here, too.
-/* Print a string when a menu item is selected */
+<sect1> Tree Item Widget<label id="sec_Tree_Item_Widget">
+<p>
+The GtkTreeItem widget, like GtkListItem, is derived from GtkItem,
+which in turn is derived from GtkBin. Therefore, the item itself is a
+generic container holding exactly one child widget, which can be of
+any type. The GtkTreeItem widget has a number of extra fields, but
+the only one we need be concerned with is the <tt>subtree</tt> field.
-static void menuitem_response (gchar *string)
+The definition for the GtkTreeItem struct looks like this:
+
+<tscreen><verb>
+struct _GtkTreeItem
{
- printf("%s\n", string);
+ GtkItem item;
+
+ GtkWidget *subtree;
+ GtkWidget *pixmaps_box;
+ GtkWidget *plus_pix_widget, *minus_pix_widget;
+
+ GList *pixmaps; /* pixmap node for this items color depth */
+
+ guint expanded : 1;
+};
+</verb></tscreen>
+
+The <tt>pixmaps_box</tt> field is a GtkEventBox which catches clicks
+on the plus/minus symbol which controls expansion and collapsing. The
+<tt>pixmaps</tt> field points to an internal data structure. Since
+you can always obtain the subtree of a GtkTreeItem in a (relatively)
+type-safe manner with the GTK_TREE_ITEM_SUBTREE (Item) macro, it's
+probably advisable never to touch the insides of a GtkTreeItem unless
+you <em>really</em> know what you're doing.
+
+Since it is directly derived from a GtkItem it can be treated as such
+by using the GTK_ITEM (TreeItem) macro. A GtkTreeItem usually holds a
+label, so the convenience function gtk_list_item_new_with_label() is
+provided. The same effect can be achieved using code like the
+following, which is actually copied verbatim from
+gtk_tree_item_new_with_label():
+
+<tscreen><verb>
+tree_item = gtk_tree_item_new ();
+label_widget = gtk_label_new (label);
+gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5);
+
+gtk_container_add (GTK_CONTAINER (tree_item), label_widget);
+gtk_widget_show (label_widget);
+</verb></tscreen>
+
+As one is not forced to add a GtkLabel to a GtkTreeItem, you could
+also add a GtkHBox or a GtkArrow, or even a GtkNotebook (though your
+app will likely be quite unpopular in this case) to the GtkTreeItem.
+
+If you remove all the items from a subtree, it will be destroyed and
+unparented, unless you reference it beforehand, and the GtkTreeItem
+which owns it will be collapsed. So, if you want it to stick around,
+do something like the following:
+
+<tscreen><verb>
+gtk_widget_ref (tree);
+owner = GTK_TREE(tree)->tree_owner;
+gtk_container_remove (GTK_CONTAINER(tree), item);
+if (tree->parent == NULL){
+ gtk_tree_item_expand (GTK_TREE_ITEM(owner));
+ gtk_tree_item_set_subtree (GTK_TREE_ITEM(owner), tree);
}
-/* example-end */
+else
+ gtk_widget_unref (tree);
</verb></tscreen>
-You may also set a menu item to be insensitive and, using an accelerator
-table, bind keys to menu functions.
+Finally, drag-n-drop <em>does</em> work with GtkTreeItems. You just
+have to make sure that the GtkTreeItem you want to make into a drag
+item or a drop site has not only been added to a GtkTree, but that
+each successive parent widget has a parent itself, all the way back to
+a toplevel or dialog window, when you call gtk_widget_dnd_drag_set()
+or gtk_widget_dnd_drop_set(). Otherwise, strange things will happen.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Using GtkMenuFactory
+<sect2> Signals
<p>
-Now that we've shown you the hard way, here's how you do it using the
-gtk_menu_factory calls.
+GtkTreeItem inherits the "select", "deselect", and "toggle" signals
+from GtkItem. In addition, it adds two signals of its own, "expand"
+and "collapse".
-<!-- ----------------------------------------------------------------- -->
-<sect1>Menu Factory Example
+<tscreen><verb>
+void select( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when an item is about to be selected, either
+after it has been clicked on by the user, or when the program calls
+gtk_tree_item_select(), gtk_item_select(), or gtk_tree_select_child().
+
+<tscreen><verb>
+void deselect( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when an item is about to be unselected, either
+after it has been clicked on by the user, or when the program calls
+gtk_tree_item_deselect() or gtk_item_deselect(). In the case of
+GtkTreeItems, it is also emitted by gtk_tree_unselect_child(), and
+sometimes gtk_tree_select_child().
+
+<tscreen><verb>
+void toggle( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the program calls gtk_item_toggle(). The
+effect it has when emitted on a GtkTreeItem is to call
+gtk_tree_select_child() (and never gtk_tree_unselect_child()) on the
+item's parent tree, if the item has a parent tree. If it doesn't,
+then the highlight is reversed on the item.
+
+<tscreen><verb>
+void expand( GtkTreeItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the tree item's subtree is about to be
+expanded, that is, when the user clicks on the plus sign next to the
+item, or when the program calls gtk_tree_item_expand().
+
+<tscreen><verb>
+void collapse( GtkTreeItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the tree item's subtree is about to be
+collapsed, that is, when the user clicks on the minus sign next to the
+item, or when the program calls gtk_tree_item_collapse().
+
+<sect2> Functions and Macros
<p>
-Here is an example using the GTK menu factory. This is the first file,
-menufactory.h. We keep a separate menufactory.c and mfmain.c because
-of the global variables used in the menufactory.c file.
+<tscreen><verb>
+guint gtk_tree_item_get_type( void );
+</verb></tscreen>
+
+Returns the `GtkTreeItem' type identifier.
<tscreen><verb>
-/* example-start menu menufactory.h */
+GtkWidget* gtk_tree_item_new( void );
+</verb></tscreen>
-#ifndef __MENUFACTORY_H__
-#define __MENUFACTORY_H__
+Create a new GtkTreeItem object. The new widget is returned as a pointer
+to a GtkWidget object. NULL is returned on failure.
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+<tscreen><verb>
+GtkWidget* gtk_tree_item_new_with_label (gchar *label);
+</verb></tscreen>
-void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
-void menus_create(GtkMenuEntry *entries, int nmenu_entries);
+Create a new GtkTreeItem object, having a single GtkLabel as
+the sole child. The new widget is returned as a pointer to a
+GtkWidget object. NULL is returned on failure.
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+<tscreen><verb>
+void gtk_tree_item_select( GtkTreeItem *tree_item );
+</verb></tscreen>
-#endif /* __MENUFACTORY_H__ */
-/* example-end */
+This function is basicaly a wrapper around a call to
+gtk_item_select (GTK_ITEM (tree_item)) which will emit the
+select signal.
+
+<tscreen><verb>
+void gtk_tree_item_deselect( GtkTreeItem *tree_item );
</verb></tscreen>
-And here is the menufactory.c file.
+This function is basicaly a wrapper around a call to
+gtk_item_deselect (GTK_ITEM (tree_item)) which will emit the
+deselect signal.
<tscreen><verb>
-/* example-start menu menufactory.c */
+void gtk_tree_item_set_subtree( GtkTreeItem *tree_item,
+ GtkWidget *subtree );
+</verb></tscreen>
-#include <gtk/gtk.h>
-#include <strings.h>
+This function adds subtree to tree_item, showing it if tree_item is
+expanded, or hiding it if tree_item is collapsed. Again, remember
+that the tree_item must have already been added to a tree for this to
+work.
-#include "mfmain.h"
+<tscreen><verb>
+void gtk_tree_item_remove_subtree( GtkTreeItem *tree_item );
+</verb></tscreen>
+This removes all of tree_item's subtree's children (thus unreferencing
+and destroying it, any of its children's subtrees, and so on...), then
+removes the subtree itself, and hides the plus/minus sign.
-static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
-static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
-void menus_init(void);
-void menus_create(GtkMenuEntry * entries, int nmenu_entries);
+<tscreen><verb>
+void gtk_tree_item_expand( GtkTreeItem *tree_item );
+</verb></tscreen>
+This emits the "expand" signal on tree_item, which expands it.
-/* this is the GtkMenuEntry structure used to create new menus. The
- * first member is the menu definition string. The second, the
- * default accelerator key used to access this menu function with
- * the keyboard. The third is the callback function to call when
- * this menu item is selected (by the accelerator key, or with the
- * mouse.) The last member is the data to pass to your callback function.
- */
+<tscreen><verb>
+void gtk_tree_item_collapse( GtkTreeItem *tree_item );
+</verb></tscreen>
-static GtkMenuEntry menu_items[] =
-{
- {"<Main>/File/New", "<control>N", NULL, NULL},
- {"<Main>/File/Open", "<control>O", NULL, NULL},
- {"<Main>/File/Save", "<control>S", NULL, NULL},
- {"<Main>/File/Save as", NULL, NULL, NULL},
- {"<Main>/File/<separator>", NULL, NULL, NULL},
- {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
- {"<Main>/Options/Test", NULL, NULL, NULL}
-};
+This emits the "collapse" signal on tree_item, which collapses it.
-/* calculate the number of menu_item's */
-static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
+<tscreen><verb>
+GtkTreeItem *GTK_TREE_ITEM (gpointer obj)
+</verb></tscreen>
-static int initialize = TRUE;
-static GtkMenuFactory *factory = NULL;
-static GtkMenuFactory *subfactory[1];
-static GHashTable *entry_ht = NULL;
+Cast a generic pointer to `GtkTreeItem*'.
-void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
+<tscreen><verb>
+GtkTreeItemClass *GTK_TREE_ITEM_CLASS (gpointer obj)
+</verb></tscreen>
+
+Cast a generic pointer to `GtkTreeItemClass'.
+
+<tscreen><verb>
+gint GTK_IS_TREE_ITEM (gpointer obj)
+</verb></tscreen>
+
+Determine if a generic pointer refers to a `GtkTreeItem' object.
+
+<tscreen><verb>
+GtkWidget GTK_TREE_ITEM_SUBTREE (gpointer obj)
+</verb></tscreen>
+
+Return's a tree item's subtree (obj should point to a `GtkTreeItem'
+object).
+
+<sect1> Tree Example
+<p>
+This is somewhat like the tree example in testgtk.c, but a lot less
+complete (although much better commented). It puts up a window with a
+tree, and connects all the signals for the relevant objects, so you
+can see when they are emitted.
+
+<tscreen><verb>
+/* example-start tree tree.c */
+
+#include <gtk/gtk.h>
+
+/* for all the GtkItem:: and GtkTreeItem:: signals */
+static void cb_itemsignal (GtkWidget *item, gchar *signame)
{
- if (initialize)
- menus_init();
-
- if (menubar)
- *menubar = subfactory[0]->widget;
- if (table)
- *table = subfactory[0]->table;
+ gchar *name;
+ GtkLabel *label;
+
+ /* It's a GtkBin, so it has one child, which we know to be a
+ label, so get that */
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ /* Get the text of the label */
+ gtk_label_get (label, &name);
+ /* Get the level of the tree which the item is in */
+ g_print ("%s called for item %s->%p, level %d\n", signame, name,
+ item, GTK_TREE (item->parent)->level);
}
-void menus_init(void)
+/* Note that this is never called */
+static void cb_unselect_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
{
- if (initialize) {
- initialize = FALSE;
-
- factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
- subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
-
- gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
- menus_create(menu_items, nmenu_items);
- }
+ g_print ("unselect_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
}
-void menus_create(GtkMenuEntry * entries, int nmenu_entries)
+/* Note that this is called every time the user clicks on an item,
+ whether it is already selected or not. */
+static void cb_select_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
{
- char *accelerator;
- int i;
-
- if (initialize)
- menus_init();
-
- if (entry_ht)
- for (i = 0; i < nmenu_entries; i++) {
- accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
- if (accelerator) {
- if (accelerator[0] == '\0')
- entries[i].accelerator = NULL;
- else
- entries[i].accelerator = accelerator;
- }
- }
- gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
-
- for (i = 0; i < nmenu_entries; i++)
- if (entries[i].widget) {
- gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
- (GtkSignalFunc) menus_install_accel,
- entries[i].path);
- gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
- (GtkSignalFunc) menus_remove_accel,
- entries[i].path);
- }
+ g_print ("select_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
}
-static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
+static void cb_selection_changed (GtkWidget *tree)
{
- char accel[64];
- char *t1, t2[2];
-
- accel[0] = '\0';
- if (modifiers & GDK_CONTROL_MASK)
- strcat(accel, "<control>");
- if (modifiers & GDK_SHIFT_MASK)
- strcat(accel, "<shift>");
- if (modifiers & GDK_MOD1_MASK)
- strcat(accel, "<alt>");
-
- t2[0] = key;
- t2[1] = '\0';
- strcat(accel, t2);
-
- if (entry_ht) {
- t1 = g_hash_table_lookup(entry_ht, path);
- g_free(t1);
- } else
- entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
-
- g_hash_table_insert(entry_ht, path, g_strdup(accel));
-
- return TRUE;
+ GList *i;
+
+ g_print ("selection_change called for tree %p\n", tree);
+ g_print ("selected objects are:\n");
+
+ i = GTK_TREE_SELECTION(tree);
+ while (i){
+ gchar *name;
+ GtkLabel *label;
+ GtkWidget *item;
+
+ /* Get a GtkWidget pointer from the list node */
+ item = GTK_WIDGET (i->data);
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ gtk_label_get (label, &name);
+ g_print ("\t%s on level %d\n", name, GTK_TREE
+ (item->parent)->level);
+ i = i->next;
+ }
}
-static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
+int main (int argc, char *argv[])
{
- char *t;
-
- if (entry_ht) {
- t = g_hash_table_lookup(entry_ht, path);
- g_free(t);
-
- g_hash_table_insert(entry_ht, path, g_strdup(""));
+ GtkWidget *window, *scrolled_win, *tree;
+ static gchar *itemnames[] = {"Foo", "Bar", "Baz", "Quux",
+ "Maurice"};
+ gint i;
+
+ gtk_init (&argc, &argv);
+
+ /* a generic toplevel window */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect (GTK_OBJECT(window), "delete_event",
+ GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
+ gtk_container_border_width (GTK_CONTAINER(window), 5);
+
+ /* A generic scrolled window */
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_usize (scrolled_win, 150, 200);
+ gtk_container_add (GTK_CONTAINER(window), scrolled_win);
+ gtk_widget_show (scrolled_win);
+
+ /* Create the root tree */
+ tree = gtk_tree_new();
+ g_print ("root tree is %p\n", tree);
+ /* connect all GtkTree:: signals */
+ gtk_signal_connect (GTK_OBJECT(tree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "selection_changed",
+ GTK_SIGNAL_FUNC(cb_selection_changed), tree);
+ /* Add it to the scrolled window */
+ gtk_container_add (GTK_CONTAINER(scrolled_win), tree);
+ /* Set the selection mode */
+ gtk_tree_set_selection_mode (GTK_TREE(tree),
+ GTK_SELECTION_MULTIPLE);
+ /* Show it */
+ gtk_widget_show (tree);
+
+ for (i = 0; i < 5; i++){
+ GtkWidget *subtree, *item;
+ gint j;
+
+ /* Create a tree item */
+ item = gtk_tree_item_new_with_label (itemnames[i]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(item), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(item), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(item), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(item), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(item), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ /* Add it to the parent tree */
+ gtk_tree_append (GTK_TREE(tree), item);
+ /* Show it - this can be done at any time */
+ gtk_widget_show (item);
+ /* Create this item's subtree */
+ subtree = gtk_tree_new();
+ g_print ("-> item %s->%p, subtree %p\n", itemnames[i], item,
+ subtree);
+
+ /* This is still necesary if you want these signals to be called
+ for the subtree's children. Note that selection_change will be
+ signalled for the root tree regardless. */
+ gtk_signal_connect (GTK_OBJECT(subtree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), subtree);
+ gtk_signal_connect (GTK_OBJECT(subtree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), subtree);
+ /* This has absolutely no effect, because it is completely ignored
+ in subtrees */
+ gtk_tree_set_selection_mode (GTK_TREE(subtree),
+ GTK_SELECTION_SINGLE);
+ /* Neither does this, but for a rather different reason - the
+ view_mode and view_line values of a tree are propagated to
+ subtrees when they are mapped. So, setting it later on would
+ actually have a (somewhat unpredictable) effect */
+ gtk_tree_set_view_mode (GTK_TREE(subtree), GTK_TREE_VIEW_ITEM);
+ /* Set this item's subtree - note that you cannot do this until
+ AFTER the item has been added to its parent tree! */
+ gtk_tree_item_set_subtree (GTK_TREE_ITEM(item), subtree);
+
+ for (j = 0; j < 5; j++){
+ GtkWidget *subitem;
+
+ /* Create a subtree item, in much the same way */
+ subitem = gtk_tree_item_new_with_label (itemnames[j]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(subitem), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(subitem), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(subitem), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(subitem), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(subitem), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ g_print ("-> -> item %s->%p\n", itemnames[j], subitem);
+ /* Add it to its parent tree */
+ gtk_tree_append (GTK_TREE(subtree), subitem);
+ /* Show it */
+ gtk_widget_show (subitem);
}
-}
+ }
-void menus_set_sensitive(char *path, int sensitive)
-{
- GtkMenuPath *menu_path;
-
- if (initialize)
- menus_init();
-
- menu_path = gtk_menu_factory_find(factory, path);
- if (menu_path)
- gtk_widget_set_sensitive(menu_path->widget, sensitive);
- else
- g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path);
+ /* Show the window and loop endlessly */
+ gtk_widget_show (window);
+ gtk_main();
+ return 0;
}
/* example-end */
</verb></tscreen>
-And here's the mfmain.h
-
-<tscreen><verb>
-/* example-start menu mfmain.h */
+<!-- ***************************************************************** -->
+<sect>Menu Widget
+<!-- ***************************************************************** -->
+<p>
+There are two ways to create menus, there's the easy way, and there's the
+hard way. Both have their uses, but you can usually use the menufactory
+(the easy way). The "hard" way is to create all the menus using the calls
+directly. The easy way is to use the gtk_menu_factory calls. This is
+much simpler, but there are advantages and disadvantages to each approach.
-#ifndef __MFMAIN_H__
-#define __MFMAIN_H__
+The menufactory is much easier to use, and to add new menus to, although
+writing a few wrapper functions to create menus using the manual method
+could go a long way towards usability. With the menufactory, it is not
+possible to add images or the character '/' to the menus.
+<!-- ----------------------------------------------------------------- -->
+<sect1>Manual Menu Creation
+<p>
+In the true tradition of teaching, we'll show you the hard
+way first. <tt>:)</>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+There are three widgets that go into making a menubar and submenus:
+<itemize>
+<item>a menu item, which is what the user wants to select, e.g. 'Save'
+<item>a menu, which acts as a container for the menu items, and
+<item>a menubar, which is a container for each of the individual menus,
+</itemize>
-void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
+This is slightly complicated by the fact that menu item widgets are used
+for two different things. They are both the widets that are packed into
+the menu, and the widget that is packed into the menubar, which,
+when selected, activiates the menu.
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+Let's look at the functions that are used to create menus and menubars.
+This first function is used to create a new menubar.
-#endif /* __MFMAIN_H__ */
-/* example-end */
+<tscreen><verb>
+GtkWidget *gtk_menu_bar_new( void );
</verb></tscreen>
-And mfmain.c
+This rather self explanatory function creates a new menubar. You use
+gtk_container_add to pack this into a window, or the box_pack functions to
+pack it into a box - the same as buttons.
<tscreen><verb>
-/* example-start menu mfmain.c */
-
-#include <gtk/gtk.h>
-
-#include "mfmain.h"
-#include "menufactory.h"
+GtkWidget *gtk_menu_new( void );
+</verb></tscreen>
+This function returns a pointer to a new menu, it is never actually shown
+(with gtk_widget_show), it is just a container for the menu items. Hopefully this will
+become more clear when you look at the example below.
-int main(int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *main_vbox;
- GtkWidget *menubar;
-
- GtkAcceleratorTable *accel;
-
- gtk_init(&argc, &argv);
-
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_signal_connect(GTK_OBJECT(window), "destroy",
- GTK_SIGNAL_FUNC(file_quit_cmd_callback),
- "WM destroy");
- gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
- gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
-
- main_vbox = gtk_vbox_new(FALSE, 1);
- gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
- gtk_container_add(GTK_CONTAINER(window), main_vbox);
- gtk_widget_show(main_vbox);
-
- get_main_menu(&menubar, &accel);
- gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
- gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
- gtk_widget_show(menubar);
-
- gtk_widget_show(window);
- gtk_main();
-
- return(0);
-}
+The next two calls are used to create menu items that are packed into
+the menu (and menubar).
-/* This is just to demonstrate how callbacks work when using the
- * menufactory. Often, people put all the callbacks from the menus
- * in a separate file, and then have them call the appropriate functions
- * from there. Keeps it more organized. */
-void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
-{
- g_print ("%s\n", (char *) data);
- gtk_exit(0);
-}
-/* example-end */
+<tscreen><verb>
+GtkWidget *gtk_menu_item_new( void );
</verb></tscreen>
-And a makefile so it'll be easier to compile it.
+and
<tscreen><verb>
-# Makefile.mf
+GtkWidget *gtk_menu_item_new_with_label( const char *label );
+</verb></tscreen>
-CC = gcc
-PROF = -g
-C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
-L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
-L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
-PROGNAME = menufactory
+These calls are used to create the menu items that are to be displayed.
+Remember to differentiate between a "menu" as created with gtk_menu_new
+and a "menu item" as created by the gtk_menu_item_new functions. The
+menu item will be an actual button with an associated action,
+whereas a menu will be a container holding menu items.
-O_FILES = menufactory.o mfmain.o
+The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after
+reading about the buttons. One creates a new menu item with a label
+already packed into it, and the other just creates a blank menu item.
-$(PROGNAME): $(O_FILES)
- rm -f $(PROGNAME)
- $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
+Once you've created a menu item you have to put it into a menu. This is
+done using the function gtk_menu_append. In order to capture when the item
+is selected by the user, we need to connect to the <tt/activate/ signal in
+the usual way. So, if we wanted to create a standard <tt/File/ menu, with
+the options <tt/Open/, <tt/Save/ and <tt/Quit/ the code would look something like
-.c.o:
- $(CC) -c $(C_FLAGS) $<
+<tscreen><verb>
+file_menu = gtk_menu_new(); /* Don't need to show menus */
-clean:
- rm -f core *.o $(PROGNAME) nohup.out
-distclean: clean
- rm -f *~
-</verb></tscreen>
+/* Create the menu items */
+open_item = gtk_menu_item_new_with_label("Open");
+save_item = gtk_menu_item_new_with_label("Save");
+quit_item = gtk_menu_item_new_with_label("Quit");
-For now, there's only this example. An explanation and lots 'o' comments
-will follow later.
+/* Add them to the menu */
+gtk_menu_append( GTK_MENU(file_menu), open_item);
+gtk_menu_append( GTK_MENU(file_menu), save_item);
+gtk_menu_append( GTK_MENU(file_menu), quit_item);
-<!-- ***************************************************************** -->
-<sect> Text Widget
-<!-- ***************************************************************** -->
-<p>
-The Text widget allows multiple lines of text to be displayed and edited.
-It supports both multi-colored and multi-font text, allowing them to be
-mixed in any way we wish. It also has a wide set of key based text editing
-commands, which are compatible with Emacs.
+/* Attach the callback functions to the activate signal */
+gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
+gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
-The text widget supports full cut-and-paste facilities, including the use
-of double- and triple-click to select a word and a whole line, respectively.
+/* We can attach the Quit menu item to our exit function */
+gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
+ GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
-<!-- ----------------------------------------------------------------- -->
-<sect1>Creating and Configuring a Text box
-<p>
-There is only one function for creating a new Text widget.
-<tscreen><verb>
-GtkWidget *gtk_text_new( GtkAdjustment *hadj,
- GtkAdjustment *vadj );
+/* We do need to show menu items */
+gtk_widget_show( open_item );
+gtk_widget_show( save_item );
+gtk_widget_show( quit_item );
</verb></tscreen>
-The arguments allow us to give the Text widget pointers to Adjustments
-that can be used to track the viewing position of the widget. Passing NULL
-values to either or both of these arguments will cause the gtk_text_new
-function to create it's own.
+At this point we have our menu. Now we need to create a menubar and a menu
+item for the <tt/File/ entry, to which we add our menu. The code looks like this
<tscreen><verb>
-void gtk_text_set_adjustments( GtkText *text,
- GtkAdjustment *hadj,
- GtkAdjustment *vadj );
+menu_bar = gtk_menu_bar_new();
+gtk_container_add( GTK_CONTAINER(window), menu_bar);
+gtk_widget_show( menu_bar );
+
+file_item = gtk_menu_item_new_with_label("File");
+gtk_widget_show(file_item);
</verb></tscreen>
-The above function allows the horizontal and vertical adjustments of a
-Text widget to be changed at any time.
+Now we need to associate the menu with <tt/file_item/. This is done with the
+function
-The text widget will not automatically create it's own scrollbars when
-the amount of text to be displayed is too long for the display window. We
-therefore have to create and add them to the display layout ourselves.
+<tscreen>
+void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
+ GtkWidget *submenu );
+</tscreen>
+
+So, our example would continue with
<tscreen><verb>
- vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
- gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
- gtk_widget_show (vscrollbar);
+gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );
</verb></tscreen>
-The above code snippet creates a new vertical scrollbar, and attaches
-it to the vertical adjustment of the text widget, <tt/text/. It then packs
-it into a box in the normal way.
+All that is left to do is to add the menu to the menubar, which is accomplished
+using the function
-Note, currently the GtkText widget does not support horizontal scrollbars.
+<tscreen>
+void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
+</tscreen>
-There are two main ways in which a Text widget can be used: to allow the
-user to edit a body of text, or to allow us to display multiple lines of
-text to the user. In order for us to switch between these modes of
-operation, the text widget has the following function:
+which in our case looks like this:
<tscreen><verb>
-void gtk_text_set_editable( GtkText *text,
- gint editable );
+gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );
</verb></tscreen>
-The <tt/editable/ argument is a TRUE or FALSE value that specifies whether
-the user is permitted to edit the contents of the Text widget. When the
-text widget is editable, it will display a cursor at the current insertion
-point.
-
-You are not, however, restricted to just using the text widget in these
-two modes. You can toggle the editable state of the text widget at any
-time, and can insert text at any time.
-
-The text widget wraps lines of text that are too long to
-fit onto a single line of the display window. It's default behaviour is
-to break words across line breaks. This can be changed using the next
-function:
+If we wanted the menu right justified on the menubar, such as help menus
+often are, we can use the following function (again on <tt/file_item/
+in the current example) before attaching it to the menubar.
<tscreen><verb>
-void gtk_text_set_word_wrap( GtkText *text,
- gint word_wrap );
+void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
</verb></tscreen>
-Using this function allows us to specify that the text widget should
-wrap long lines on word boundaries. The <tt/word_wrap/ argument is a
-TRUE or FALSE value.
+Here is a summary of the steps needed to create a menu bar with menus attached:
-<!-- ----------------------------------------------------------------- -->
-<sect1>Text Manipulation
-<P>
-The current insertion point of a Text widget can be set using
-<tscreen><verb>
-void gtk_text_set_point( GtkText *text,
- guint index );
-</verb></tscreen>
+<itemize>
+<item> Create a new menu using gtk_menu_new()
+<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have
+on your menu. And use gtk_menu_append() to put each of these new items on
+to the menu.
+<item> Create a menu item using gtk_menu_item_new(). This will be the root of
+the menu, the text appearing here will be on the menubar itself.
+<item>Use gtk_menu_item_set_submenu() to attach the menu to the root menu
+item (the one created in the above step).
+<item> Create a new menubar using gtk_menu_bar_new. This step only needs
+to be done once when creating a series of menus on one menu bar.
+<item> Use gtk_menu_bar_append to put the root menu onto the menubar.
+</itemize>
-where <tt/index/ is the position to set the insertion point.
+Creating a popup menu is nearly the same. The difference is that the
+menu is not posted `automatically' by a menubar, but explicitly by calling
+the function gtk_menu_popup() from a button-press event, for example.
+Take these steps:
-Analogous to this is the function for getting the current insertion point:
+<itemize>
+<item>Create an event handling function. It needs to have the prototype
+<tscreen>
+static gint handler( GtkWidget *widget,
+ GdkEvent *event );
+</tscreen>
+and it will use the event to find out where to pop up the menu.
+<item>In the event handler, if the event is a mouse button press, treat
+<tt>event</tt> as a button event (which it is) and use it as
+shown in the sample code to pass information to gtk_menu_popup().
+<item>Bind that event handler to a widget with
+<tscreen>
+gtk_signal_connect_object(GTK_OBJECT(widget), "event",
+ GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
+</tscreen>
+where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt>
+is the handling function, and <tt>menu</tt> is a menu created with
+gtk_menu_new(). This can be a menu which is also posted by a menu bar,
+as shown in the sample code.
+</itemize>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Manual Menu Example
+<p>
+That should about do it. Let's take a look at an example to help clarify.
<tscreen><verb>
-guint gtk_text_get_point( GtkText *text );
-</verb></tscreen>
+/* example-start menu menu.c */
-A function that is useful in combination with the above two functions is
+#include <gtk/gtk.h>
-<tscreen><verb>
-guint gtk_text_get_length( GtkText *text );
-</verb></tscreen>
+static gint button_press (GtkWidget *, GdkEvent *);
+static void menuitem_response (gchar *);
-which returns the current length of the Text widget. The length is the
-number of characters that are within the text block of the widget,
-including characters such as carriage-return, which marks the end of lines.
+int main (int argc, char *argv[])
+{
-In order to insert text at the current insertion point of a Text
-widget, the function gtk_text_insert is used, which also allows us to
-specify background and foreground colors and a font for the text.
+ GtkWidget *window;
+ GtkWidget *menu;
+ GtkWidget *menu_bar;
+ GtkWidget *root_menu;
+ GtkWidget *menu_items;
+ GtkWidget *vbox;
+ GtkWidget *button;
+ char buf[128];
+ int i;
-<tscreen><verb>
-void gtk_text_insert( GtkText *text,
- GdkFont *font,
- GdkColor *fore,
- GdkColor *back,
- const char *chars,
- gint length );
-</verb></tscreen>
+ gtk_init (&argc, &argv);
-Passing a value of <tt/NULL/ in as the value for the foreground color,
-background colour or font will result in the values set within the widget
-style to be used. Using a value of <tt/-1/ for the length parameter will
-result in the whole of the text string given being inserted.
+ /* create a new window */
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
+ gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
+ gtk_signal_connect(GTK_OBJECT (window), "delete_event",
+ (GtkSignalFunc) gtk_main_quit, NULL);
-The text widget is one of the few within GTK that redraws itself
-dynamically, outside of the gtk_main function. This means that all changes
-to the contents of the text widget take effect immediately. This may be
-undesirable when performing multiple changes to the text widget. In order
-to allow us to perform multiple updates to the text widget without it
-continuously redrawing, we can freeze the widget, which temporarily stops
-it from automatically redrawing itself every time it is changed. We can
-then thaw the widget after our updates are complete.
+ /* Init the menu-widget, and remember -- never
+ * gtk_show_widget() the menu widget!!
+ * This is the menu that holds the menu items, the one that
+ * will pop up when you click on the "Root Menu" in the app */
+ menu = gtk_menu_new();
-The following two functions perform this freeze and thaw action:
+ /* Next we make a little loop that makes three menu-entries for "test-menu".
+ * Notice the call to gtk_menu_append. Here we are adding a list of
+ * menu items to our menu. Normally, we'd also catch the "clicked"
+ * signal on each of the menu items and setup a callback for it,
+ * but it's omitted here to save space. */
-<tscreen><verb>
-void gtk_text_freeze( GtkText *text );
+ for(i = 0; i < 3; i++)
+ {
+ /* Copy the names to the buf. */
+ sprintf(buf, "Test-undermenu - %d", i);
-void gtk_text_thaw( GtkText *text );
-</verb></tscreen>
+ /* Create a new menu-item with a name... */
+ menu_items = gtk_menu_item_new_with_label(buf);
-Text is deleted from the text widget relative to the current insertion
-point by the following two functions. The return value is a TRUE or
-FALSE indicator of whether the operation was successful.
+ /* ...and add it to the menu. */
+ gtk_menu_append(GTK_MENU (menu), menu_items);
-<tscreen><verb>
-gint gtk_text_backward_delete( GtkText *text,
- guint nchars );
+ /* Do something interesting when the menuitem is selected */
+ gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
-gint gtk_text_forward_delete ( GtkText *text,
- guint nchars );
-</verb></tscreen>
+ /* Show the widget */
+ gtk_widget_show(menu_items);
+ }
-If you want to retrieve the contents of the text widget, then the macro
-<tt/GTK_TEXT_INDEX(t, index)/ allows you to retrieve the character at
-position <tt/index/ within the text widget <tt/t/.
+ /* This is the root menu, and will be the label
+ * displayed on the menu bar. There won't be a signal handler attached,
+ * as it only pops up the rest of the menu when pressed. */
+ root_menu = gtk_menu_item_new_with_label("Root Menu");
-To retrieve larger blocks of text, we can use the function
+ gtk_widget_show(root_menu);
-<tscreen><verb>
-gchar *gtk_editable_get_chars( GtkEditable *editable,
- gint start_pos,
- gint end_pos );
-</verb></tscreen>
+ /* Now we specify that we want our newly created "menu" to be the menu
+ * for the "root menu" */
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
-This is a function of the parent class of the text widget. A value of -1 as
-<tt/end_pos/ signifies the end of the text. The index of the text starts at 0.
+ /* A vbox to put a menu and a button in: */
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+ gtk_widget_show(vbox);
-The function allocates a new chunk of memory for the text block, so don't forget
-to free it with a call to g_free when you have finished with it.
-
-<!-- ----------------------------------------------------------------- -->
-<sect1>Keyboard Shortcuts
-<p>
-The text widget has a number of pre-installed keyboard shotcuts for common
-editing, motion and selection functions. These are accessed using Control
-and Alt key combinations.
+ /* Create a menu-bar to hold the menus and add it to our main window */
+ menu_bar = gtk_menu_bar_new();
+ gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
+ gtk_widget_show(menu_bar);
-In addition to these, holding down the Control key whilst using cursor key
-movement will move the cursor by words rather than characters. Holding down
-Shift whilst using cursor movement will extend the selection.
+ /* Create a button to which to attach menu as a popup */
+ button = gtk_button_new_with_label("press me");
+ gtk_signal_connect_object(GTK_OBJECT(button), "event",
+ GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
+ gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
+ gtk_widget_show(button);
-<sect2>Motion Shotcuts
-<p>
-<itemize>
-<item> Ctrl-A Beginning of line
-<item> Ctrl-E End of line
-<item> Ctrl-N Next Line
-<item> Ctrl-P Previous Line
-<item> Ctrl-B Backward one character
-<item> Ctrl-F Forward one character
-<item> Alt-B Backward one word
-<item> Alt-F Forward one word
-</itemize>
+ /* And finally we append the menu-item to the menu-bar -- this is the
+ * "root" menu-item I have been raving about =) */
+ gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
-<sect2>Editing Shortcuts
-<p>
-<itemize>
-<item> Ctrl-H Delete Backward Character (Backspace)
-<item> Ctrl-D Delete Forward Character (Delete)
-<item> Ctrl-W Delete Backward Word
-<item> Alt-D Delete Forward Word
-<item> Ctrl-K Delete to end of line
-<item> Ctrl-U Delete line
-</itemize>
+ /* always display the window as the last step so it all splashes on
+ * the screen at once. */
+ gtk_widget_show(window);
-<sect2>Selection Shortcuts
-<p>
-<itemize>
-<item> Ctrl-X Cut to clipboard
-<item> Ctrl-C Copy to clipboard
-<item> Ctrl-V Paste from clipboard
-</itemize>
+ gtk_main ();
-<!-- ***************************************************************** -->
-<sect> Undocumented Widgets
-<!-- ***************************************************************** -->
-<p>
-These all require authors! :) Please consider contributing to our tutorial.
+ return 0;
+}
-If you must use one of these widgets that are undocumented, I strongly
-suggest you take a look at their respective header files in the GTK
-distribution. GTK's function names are very descriptive. Once you have an
-understanding of how things work, it's not difficult to figure out how to
-use a widget simply by looking at it's function declarations. This, along
-with a few examples from others' code, and it should be no problem.
+/* Respond to a button-press by posting a menu passed in as widget.
+ *
+ * Note that the "widget" argument is the menu being posted, NOT
+ * the button that was pressed.
+ */
-When you do come to understand all the functions of a new undocumented
-widget, please consider writing a tutorial on it so others may benifit
-from your time.
+static gint button_press (GtkWidget *widget, GdkEvent *event)
+{
-<!-- ----------------------------------------------------------------- -->
-<sect1> Adjustments
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Toolbar
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Fixed Container
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Range Controls
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Curves
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Previews
-<p>
+ if (event->type == GDK_BUTTON_PRESS) {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
+ bevent->button, bevent->time);
+ /* Tell calling code that we have handled this event; the buck
+ * stops here. */
+ return TRUE;
+ }
-(This may need to be rewritten to follow the style of the rest of the tutorial)
+ /* Tell calling code that we have not handled this event; pass it on. */
+ return FALSE;
+}
-<tscreen><verb>
-Previews serve a number of purposes in GIMP/GTK. The most important one is
-this. High quality images may take up to tens of megabytes of memory - easy!
-Any operation on an image that big is bound to take a long time. If it takes
-you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after
-you make an error) to choose the desired modification, it make take you
-literally hours to make the right one - if you don't run out of memory
-first. People who have spent hours in color darkrooms know the feeling.
-Previews to the rescue!
+/* Print a string when a menu item is selected */
-But the annoyance of the delay is not the only issue. Oftentimes it is
-helpful to compare the Before and After versions side-by-side or at least
-back-to-back. If you're working with big images and 10 second delays,
-obtaining the Before and After impressions is, to say the least, difficult.
-For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right
-out for most people, while back-to-back is more like back-to-1001, 1002,
-..., 1010-back! Previews to the rescue!
+static void menuitem_response (gchar *string)
+{
+ printf("%s\n", string);
+}
+/* example-end */
+</verb></tscreen>
-But there's more. Previews allow for side-by-side pre-previews. In other
-words, you write a plug-in (e.g. the filterpack simulation) which would have
-a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
-An approach like this acts as a sort of a preview palette and is very
-effective fow subtle changes. Let's go previews!
+You may also set a menu item to be insensitive and, using an accelerator
+table, bind keys to menu functions.
-There's more. For certain plug-ins real-time image-specific human
-intervention maybe necessary. In the SuperNova plug-in, for example, the
-user is asked to enter the coordinates of the center of the future
-supernova. The easiest way to do this, really, is to present the user with a
-preview and ask him to intereactively select the spot. Let's go previews!
+<!-- ----------------------------------------------------------------- -->
+<sect1>Using GtkMenuFactory
+<p>
+Now that we've shown you the hard way, here's how you do it using the
+gtk_menu_factory calls.
-Finally, a couple of misc uses. One can use previews even when not working
-with big images. For example, they are useful when rendering compicated
-patterns. (Just check out the venerable Diffraction plug-in + many other
-ones!) As another example, take a look at the colormap rotation plug-in
-(work in progress). You can also use previews for little logo's inside you
-plug-ins and even for an image of yourself, The Author. Let's go previews!
+<!-- ----------------------------------------------------------------- -->
+<sect1>Menu Factory Example
+<p>
+Here is an example using the GTK menu factory. This is the first file,
+menufactory.h. We keep a separate menufactory.c and mfmain.c because
+of the global variables used in the menufactory.c file.
-When Not to Use Previews
+<tscreen><verb>
+/* example-start menu menufactory.h */
-Don't use previews for graphs, drawing etc. GDK is much faster for that. Use
-previews only for rendered images!
+#ifndef __MENUFACTORY_H__
+#define __MENUFACTORY_H__
-Let's go previews!
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-You can stick a preview into just about anything. In a vbox, an hbox, a
-table, a button, etc. But they look their best in tight frames around them.
-Previews by themselves do not have borders and look flat without them. (Of
-course, if the flat look is what you want...) Tight frames provide the
-necessary borders.
+void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
+void menus_create(GtkMenuEntry *entries, int nmenu_entries);
- [Image][Image]
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-Previews in many ways are like any other widgets in GTK (whatever that
-means) except they possess an addtional feature: they need to be filled with
-some sort of an image! First, we will deal exclusively with the GTK aspect
-of previews and then we'll discuss how to fill them.
+#endif /* __MENUFACTORY_H__ */
+/* example-end */
+</verb></tscreen>
-GtkWidget *preview!
+And here is the menufactory.c file.
-Without any ado:
+<tscreen><verb>
+/* example-start menu menufactory.c */
- /* Create a preview widget,
- set its size, an show it */
-GtkWidget *preview;
-preview=gtk_preview_new(GTK_PREVIEW_COLOR)
- /*Other option:
- GTK_PREVIEW_GRAYSCALE);*/
-gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
-gtk_widget_show(preview);
-my_preview_rendering_function(preview);
+#include <gtk/gtk.h>
+#include <strings.h>
-Oh yeah, like I said, previews look good inside frames, so how about:
+#include "mfmain.h"
-GtkWidget *create_a_preview(int Width,
- int Height,
- int Colorfulness)
-{
- GtkWidget *preview;
- GtkWidget *frame;
-
- frame = gtk_frame_new(NULL);
- gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
- gtk_container_border_width (GTK_CONTAINER(frame),0);
- gtk_widget_show(frame);
- preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
- :GTK_PREVIEW_GRAYSCALE);
- gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
- gtk_container_add(GTK_CONTAINER(frame),preview);
- gtk_widget_show(preview);
+static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
+static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
+void menus_init(void);
+void menus_create(GtkMenuEntry * entries, int nmenu_entries);
- my_preview_rendering_function(preview);
- return frame;
-}
-That's my basic preview. This routine returns the "parent" frame so you can
-place it somewhere else in your interface. Of course, you can pass the
-parent frame to this routine as a parameter. In many situations, however,
-the contents of the preview are changed continually by your application. In
-this case you may want to pass a pointer to the preview to a
-"create_a_preview()" and thus have control of it later.
+/* this is the GtkMenuEntry structure used to create new menus. The
+ * first member is the menu definition string. The second, the
+ * default accelerator key used to access this menu function with
+ * the keyboard. The third is the callback function to call when
+ * this menu item is selected (by the accelerator key, or with the
+ * mouse.) The last member is the data to pass to your callback function.
+ */
-One more important note that may one day save you a lot of time. Sometimes
-it is desirable to label you preview. For example, you may label the preview
-containing the original image as "Original" and the one containing the
-modified image as "Less Original". It might occure to you to pack the
-preview along with the appropriate label into a vbox. The unexpected caveat
-is that if the label is wider than the preview (which may happen for a
-variety of reasons unforseeable to you, from the dynamic decision on the
-size of the preview to the size of the font) the frame expands and no longer
-fits tightly over the preview. The same problem can probably arise in other
-situations as well.
+static GtkMenuEntry menu_items[] =
+{
+ {"<Main>/File/New", "<control>N", NULL, NULL},
+ {"<Main>/File/Open", "<control>O", NULL, NULL},
+ {"<Main>/File/Save", "<control>S", NULL, NULL},
+ {"<Main>/File/Save as", NULL, NULL, NULL},
+ {"<Main>/File/<separator>", NULL, NULL, NULL},
+ {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
+ {"<Main>/Options/Test", NULL, NULL, NULL}
+};
- [Image]
+/* calculate the number of menu_item's */
+static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
-The solution is to place the preview and the label into a 2x1 table and by
-attaching them with the following paramters (this is one possible variations
-of course. The key is no GTK_FILL in the second attachment):
+static int initialize = TRUE;
+static GtkMenuFactory *factory = NULL;
+static GtkMenuFactory *subfactory[1];
+static GHashTable *entry_ht = NULL;
-gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
- 0,
- GTK_EXPAND|GTK_FILL,
- 0,0);
-gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
- GTK_EXPAND,
- GTK_EXPAND,
- 0,0);
+void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
+{
+ if (initialize)
+ menus_init();
+
+ if (menubar)
+ *menubar = subfactory[0]->widget;
+ if (table)
+ *table = subfactory[0]->table;
+}
+
+void menus_init(void)
+{
+ if (initialize) {
+ initialize = FALSE;
+
+ factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
+ subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
+
+ gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
+ menus_create(menu_items, nmenu_items);
+ }
+}
+
+void menus_create(GtkMenuEntry * entries, int nmenu_entries)
+{
+ char *accelerator;
+ int i;
+
+ if (initialize)
+ menus_init();
+
+ if (entry_ht)
+ for (i = 0; i < nmenu_entries; i++) {
+ accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
+ if (accelerator) {
+ if (accelerator[0] == '\0')
+ entries[i].accelerator = NULL;
+ else
+ entries[i].accelerator = accelerator;
+ }
+ }
+ gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
+
+ for (i = 0; i < nmenu_entries; i++)
+ if (entries[i].widget) {
+ gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
+ (GtkSignalFunc) menus_install_accel,
+ entries[i].path);
+ gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
+ (GtkSignalFunc) menus_remove_accel,
+ entries[i].path);
+ }
+}
+
+static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
+{
+ char accel[64];
+ char *t1, t2[2];
+
+ accel[0] = '\0';
+ if (modifiers & GDK_CONTROL_MASK)
+ strcat(accel, "<control>");
+ if (modifiers & GDK_SHIFT_MASK)
+ strcat(accel, "<shift>");
+ if (modifiers & GDK_MOD1_MASK)
+ strcat(accel, "<alt>");
+
+ t2[0] = key;
+ t2[1] = '\0';
+ strcat(accel, t2);
+
+ if (entry_ht) {
+ t1 = g_hash_table_lookup(entry_ht, path);
+ g_free(t1);
+ } else
+ entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_hash_table_insert(entry_ht, path, g_strdup(accel));
+
+ return TRUE;
+}
+
+static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
+{
+ char *t;
+
+ if (entry_ht) {
+ t = g_hash_table_lookup(entry_ht, path);
+ g_free(t);
+
+ g_hash_table_insert(entry_ht, path, g_strdup(""));
+ }
+}
+
+void menus_set_sensitive(char *path, int sensitive)
+{
+ GtkMenuPath *menu_path;
+
+ if (initialize)
+ menus_init();
+
+ menu_path = gtk_menu_factory_find(factory, path);
+ if (menu_path)
+ gtk_widget_set_sensitive(menu_path->widget, sensitive);
+ else
+ g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path);
+}
+/* example-end */
+</verb></tscreen>
+
+And here's the mfmain.h
+
+<tscreen><verb>
+/* example-start menu mfmain.h */
+
+#ifndef __MFMAIN_H__
+#define __MFMAIN_H__
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MFMAIN_H__ */
+/* example-end */
+</verb></tscreen>
+
+And mfmain.c
+
+<tscreen><verb>
+/* example-start menu mfmain.c */
+
+#include <gtk/gtk.h>
+
+#include "mfmain.h"
+#include "menufactory.h"
+
+
+int main(int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *main_vbox;
+ GtkWidget *menubar;
+
+ GtkAcceleratorTable *accel;
+
+ gtk_init(&argc, &argv);
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(file_quit_cmd_callback),
+ "WM destroy");
+ gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
+ gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
+
+ main_vbox = gtk_vbox_new(FALSE, 1);
+ gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
+ gtk_container_add(GTK_CONTAINER(window), main_vbox);
+ gtk_widget_show(main_vbox);
+
+ get_main_menu(&menubar, &accel);
+ gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
+ gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
+ gtk_widget_show(menubar);
+
+ gtk_widget_show(window);
+ gtk_main();
+
+ return(0);
+}
+
+/* This is just to demonstrate how callbacks work when using the
+ * menufactory. Often, people put all the callbacks from the menus
+ * in a separate file, and then have them call the appropriate functions
+ * from there. Keeps it more organized. */
+void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
+{
+ g_print ("%s\n", (char *) data);
+ gtk_exit(0);
+}
+/* example-end */
+</verb></tscreen>
+
+And a makefile so it'll be easier to compile it.
+
+<tscreen><verb>
+# Makefile.mf
+
+CC = gcc
+PROF = -g
+C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
+L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
+L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
+PROGNAME = menufactory
+
+O_FILES = menufactory.o mfmain.o
+
+$(PROGNAME): $(O_FILES)
+ rm -f $(PROGNAME)
+ $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
+
+.c.o:
+ $(CC) -c $(C_FLAGS) $<
+
+clean:
+ rm -f core *.o $(PROGNAME) nohup.out
+distclean: clean
+ rm -f *~
+</verb></tscreen>
+
+For now, there's only this example. An explanation and lots 'o' comments
+will follow later.
+
+<!-- ***************************************************************** -->
+<sect> Text Widget
+<!-- ***************************************************************** -->
+<p>
+The Text widget allows multiple lines of text to be displayed and edited.
+It supports both multi-colored and multi-font text, allowing them to be
+mixed in any way we wish. It also has a wide set of key based text editing
+commands, which are compatible with Emacs.
+
+The text widget supports full cut-and-paste facilities, including the use
+of double- and triple-click to select a word and a whole line, respectively.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Creating and Configuring a Text box
+<p>
+There is only one function for creating a new Text widget.
+<tscreen><verb>
+GtkWidget *gtk_text_new( GtkAdjustment *hadj,
+ GtkAdjustment *vadj );
+</verb></tscreen>
+
+The arguments allow us to give the Text widget pointers to Adjustments
+that can be used to track the viewing position of the widget. Passing NULL
+values to either or both of these arguments will cause the gtk_text_new
+function to create it's own.
+
+<tscreen><verb>
+void gtk_text_set_adjustments( GtkText *text,
+ GtkAdjustment *hadj,
+ GtkAdjustment *vadj );
+</verb></tscreen>
+
+The above function allows the horizontal and vertical adjustments of a
+Text widget to be changed at any time.
+
+The text widget will not automatically create it's own scrollbars when
+the amount of text to be displayed is too long for the display window. We
+therefore have to create and add them to the display layout ourselves.
+
+<tscreen><verb>
+ vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
+ gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
+ gtk_widget_show (vscrollbar);
+</verb></tscreen>
+
+The above code snippet creates a new vertical scrollbar, and attaches
+it to the vertical adjustment of the text widget, <tt/text/. It then packs
+it into a box in the normal way.
+
+Note, currently the GtkText widget does not support horizontal scrollbars.
+
+There are two main ways in which a Text widget can be used: to allow the
+user to edit a body of text, or to allow us to display multiple lines of
+text to the user. In order for us to switch between these modes of
+operation, the text widget has the following function:
+
+<tscreen><verb>
+void gtk_text_set_editable( GtkText *text,
+ gint editable );
+</verb></tscreen>
+
+The <tt/editable/ argument is a TRUE or FALSE value that specifies whether
+the user is permitted to edit the contents of the Text widget. When the
+text widget is editable, it will display a cursor at the current insertion
+point.
+
+You are not, however, restricted to just using the text widget in these
+two modes. You can toggle the editable state of the text widget at any
+time, and can insert text at any time.
+
+The text widget wraps lines of text that are too long to
+fit onto a single line of the display window. It's default behaviour is
+to break words across line breaks. This can be changed using the next
+function:
+
+<tscreen><verb>
+void gtk_text_set_word_wrap( GtkText *text,
+ gint word_wrap );
+</verb></tscreen>
+
+Using this function allows us to specify that the text widget should
+wrap long lines on word boundaries. The <tt/word_wrap/ argument is a
+TRUE or FALSE value.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Text Manipulation
+<P>
+The current insertion point of a Text widget can be set using
+<tscreen><verb>
+void gtk_text_set_point( GtkText *text,
+ guint index );
+</verb></tscreen>
+
+where <tt/index/ is the position to set the insertion point.
+
+Analogous to this is the function for getting the current insertion point:
+
+<tscreen><verb>
+guint gtk_text_get_point( GtkText *text );
+</verb></tscreen>
+
+A function that is useful in combination with the above two functions is
+
+<tscreen><verb>
+guint gtk_text_get_length( GtkText *text );
+</verb></tscreen>
+
+which returns the current length of the Text widget. The length is the
+number of characters that are within the text block of the widget,
+including characters such as carriage-return, which marks the end of lines.
+
+In order to insert text at the current insertion point of a Text
+widget, the function gtk_text_insert is used, which also allows us to
+specify background and foreground colors and a font for the text.
+
+<tscreen><verb>
+void gtk_text_insert( GtkText *text,
+ GdkFont *font,
+ GdkColor *fore,
+ GdkColor *back,
+ const char *chars,
+ gint length );
+</verb></tscreen>
+
+Passing a value of <tt/NULL/ in as the value for the foreground color,
+background colour or font will result in the values set within the widget
+style to be used. Using a value of <tt/-1/ for the length parameter will
+result in the whole of the text string given being inserted.
+
+The text widget is one of the few within GTK that redraws itself
+dynamically, outside of the gtk_main function. This means that all changes
+to the contents of the text widget take effect immediately. This may be
+undesirable when performing multiple changes to the text widget. In order
+to allow us to perform multiple updates to the text widget without it
+continuously redrawing, we can freeze the widget, which temporarily stops
+it from automatically redrawing itself every time it is changed. We can
+then thaw the widget after our updates are complete.
+
+The following two functions perform this freeze and thaw action:
+
+<tscreen><verb>
+void gtk_text_freeze( GtkText *text );
+
+void gtk_text_thaw( GtkText *text );
+</verb></tscreen>
+
+Text is deleted from the text widget relative to the current insertion
+point by the following two functions. The return value is a TRUE or
+FALSE indicator of whether the operation was successful.
+
+<tscreen><verb>
+gint gtk_text_backward_delete( GtkText *text,
+ guint nchars );
+
+gint gtk_text_forward_delete ( GtkText *text,
+ guint nchars );
+</verb></tscreen>
+
+If you want to retrieve the contents of the text widget, then the macro
+<tt/GTK_TEXT_INDEX(t, index)/ allows you to retrieve the character at
+position <tt/index/ within the text widget <tt/t/.
+
+To retrieve larger blocks of text, we can use the function
+
+<tscreen><verb>
+gchar *gtk_editable_get_chars( GtkEditable *editable,
+ gint start_pos,
+ gint end_pos );
+</verb></tscreen>
+
+This is a function of the parent class of the text widget. A value of -1 as
+<tt/end_pos/ signifies the end of the text. The index of the text starts at 0.
+
+The function allocates a new chunk of memory for the text block, so don't forget
+to free it with a call to g_free when you have finished with it.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Keyboard Shortcuts
+<p>
+The text widget has a number of pre-installed keyboard shotcuts for common
+editing, motion and selection functions. These are accessed using Control
+and Alt key combinations.
+
+In addition to these, holding down the Control key whilst using cursor key
+movement will move the cursor by words rather than characters. Holding down
+Shift whilst using cursor movement will extend the selection.
+
+<sect2>Motion Shotcuts
+<p>
+<itemize>
+<item> Ctrl-A Beginning of line
+<item> Ctrl-E End of line
+<item> Ctrl-N Next Line
+<item> Ctrl-P Previous Line
+<item> Ctrl-B Backward one character
+<item> Ctrl-F Forward one character
+<item> Alt-B Backward one word
+<item> Alt-F Forward one word
+</itemize>
+
+<sect2>Editing Shortcuts
+<p>
+<itemize>
+<item> Ctrl-H Delete Backward Character (Backspace)
+<item> Ctrl-D Delete Forward Character (Delete)
+<item> Ctrl-W Delete Backward Word
+<item> Alt-D Delete Forward Word
+<item> Ctrl-K Delete to end of line
+<item> Ctrl-U Delete line
+</itemize>
+
+<sect2>Selection Shortcuts
+<p>
+<itemize>
+<item> Ctrl-X Cut to clipboard
+<item> Ctrl-C Copy to clipboard
+<item> Ctrl-V Paste from clipboard
+</itemize>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>A GtkText Example
+<p>
+<tscreen><verb>
+/* example-start text text.c */
+
+/* text.c */
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+
+void text_toggle_editable (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_editable(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void text_toggle_word_wrap (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_word_wrap(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void close_application( GtkWidget *widget, gpointer data )
+{
+ gtk_main_quit();
+}
+
+int main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *box1;
+ GtkWidget *box2;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *check;
+ GtkWidget *separator;
+ GtkWidget *table;
+ GtkWidget *vscrollbar;
+ GtkWidget *text;
+ GdkColormap *cmap;
+ GdkColor colour;
+ GdkFont *fixed_font;
+
+ FILE *infile;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_usize (window, 600, 500);
+ gtk_window_set_policy (GTK_WINDOW(window), TRUE, TRUE, FALSE);
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_window_set_title (GTK_WINDOW (window), "Text Widget Example");
+ gtk_container_border_width (GTK_CONTAINER (window), 0);
+
+
+ box1 = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), box1);
+ gtk_widget_show (box1);
+
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
+ gtk_widget_show (box2);
+
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2);
+ gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2);
+ gtk_box_pack_start (GTK_BOX (box2), table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* Create the GtkText widget */
+ text = gtk_text_new (NULL, NULL);
+ gtk_text_set_editable (GTK_TEXT (text), TRUE);
+ gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (text);
+
+ /* Add a vertical scrollbar to the GtkText widget */
+ vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
+ gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1,
+ GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (vscrollbar);
+
+ /* Get the system colour map and allocate the colour red */
+ cmap = gdk_colormap_get_system();
+ colour.red = 0xffff;
+ colour.green = 0;
+ colour.blue = 0;
+ if (!gdk_color_alloc(cmap, &colour)) {
+ g_error("couldn't allocate colour");
+ }
+
+ /* Load a fixed font */
+ fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*");
+
+ /* Realizing a widget creates a window for it, ready for us to insert some text */
+ gtk_widget_realize (text);
+
+ /* Freeze the text widget, ready for multiple updates */
+ gtk_text_freeze (GTK_TEXT (text));
+
+ /* Insert some coloured text */
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "Supports ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &colour, NULL,
+ "colored ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "text and different ", -1);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, &text->style->black, NULL,
+ "fonts\n\n", -1);
+
+ /* Load the file text.c into the text window */
+
+ infile = fopen("text.c", "r");
+
+ if (infile) {
+ char buffer[1024];
+ int nchars;
+
+ while (1)
+ {
+ nchars = fread(buffer, 1, 1024, infile);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, NULL,
+ NULL, buffer, nchars);
+
+ if (nchars < 1024)
+ break;
+ }
+
+ fclose (infile);
+ }
+
+ /* Thaw the text widget, allowing the updates to become visible */
+ gtk_text_thaw (GTK_TEXT (text));
+
+ hbox = gtk_hbutton_box_new ();
+ gtk_box_pack_start (GTK_BOX (box2), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ check = gtk_check_button_new_with_label("Editable");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, FALSE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_editable), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
+ gtk_widget_show (check);
+ check = gtk_check_button_new_with_label("Wrap Words");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, TRUE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_word_wrap), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), FALSE);
+ gtk_widget_show (check);
+
+ separator = gtk_hseparator_new ();
+ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0);
+ gtk_widget_show (separator);
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0);
+ gtk_widget_show (box2);
+
+ button = gtk_button_new_with_label ("close");
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_widget_grab_default (button);
+ gtk_widget_show (button);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
+
+
+<!-- ***************************************************************** -->
+<sect> Undocumented Widgets
+<!-- ***************************************************************** -->
+<p>
+These all require authors! :) Please consider contributing to our tutorial.
+
+If you must use one of these widgets that are undocumented, I strongly
+suggest you take a look at their respective header files in the GTK
+distribution. GTK's function names are very descriptive. Once you have an
+understanding of how things work, it's not difficult to figure out how to
+use a widget simply by looking at it's function declarations. This, along
+with a few examples from others' code, and it should be no problem.
+
+When you do come to understand all the functions of a new undocumented
+widget, please consider writing a tutorial on it so others may benifit
+from your time.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Adjustments
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Toolbar
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Fixed Container
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Range Controls
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Curves
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Previews
+<p>
+
+(This may need to be rewritten to follow the style of the rest of the tutorial)
+
+<tscreen><verb>
+
+Previews serve a number of purposes in GIMP/GTK. The most important one is
+this. High quality images may take up to tens of megabytes of memory - easy!
+Any operation on an image that big is bound to take a long time. If it takes
+you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after
+you make an error) to choose the desired modification, it make take you
+literally hours to make the right one - if you don't run out of memory
+first. People who have spent hours in color darkrooms know the feeling.
+Previews to the rescue!
+
+But the annoyance of the delay is not the only issue. Oftentimes it is
+helpful to compare the Before and After versions side-by-side or at least
+back-to-back. If you're working with big images and 10 second delays,
+obtaining the Before and After impressions is, to say the least, difficult.
+For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right
+out for most people, while back-to-back is more like back-to-1001, 1002,
+..., 1010-back! Previews to the rescue!
+
+But there's more. Previews allow for side-by-side pre-previews. In other
+words, you write a plug-in (e.g. the filterpack simulation) which would have
+a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
+An approach like this acts as a sort of a preview palette and is very
+effective fow subtle changes. Let's go previews!
+
+There's more. For certain plug-ins real-time image-specific human
+intervention maybe necessary. In the SuperNova plug-in, for example, the
+user is asked to enter the coordinates of the center of the future
+supernova. The easiest way to do this, really, is to present the user with a
+preview and ask him to intereactively select the spot. Let's go previews!
+
+Finally, a couple of misc uses. One can use previews even when not working
+with big images. For example, they are useful when rendering compicated
+patterns. (Just check out the venerable Diffraction plug-in + many other
+ones!) As another example, take a look at the colormap rotation plug-in
+(work in progress). You can also use previews for little logo's inside you
+plug-ins and even for an image of yourself, The Author. Let's go previews!
+
+When Not to Use Previews
+
+Don't use previews for graphs, drawing etc. GDK is much faster for that. Use
+previews only for rendered images!
+
+Let's go previews!
+
+You can stick a preview into just about anything. In a vbox, an hbox, a
+table, a button, etc. But they look their best in tight frames around them.
+Previews by themselves do not have borders and look flat without them. (Of
+course, if the flat look is what you want...) Tight frames provide the
+necessary borders.
+
+ [Image][Image]
+
+Previews in many ways are like any other widgets in GTK (whatever that
+means) except they possess an addtional feature: they need to be filled with
+some sort of an image! First, we will deal exclusively with the GTK aspect
+of previews and then we'll discuss how to fill them.
+
+GtkWidget *preview!
+
+Without any ado:
+
+ /* Create a preview widget,
+ set its size, an show it */
+GtkWidget *preview;
+preview=gtk_preview_new(GTK_PREVIEW_COLOR)
+ /*Other option:
+ GTK_PREVIEW_GRAYSCALE);*/
+gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
+gtk_widget_show(preview);
+my_preview_rendering_function(preview);
+
+Oh yeah, like I said, previews look good inside frames, so how about:
+
+GtkWidget *create_a_preview(int Width,
+ int Height,
+ int Colorfulness)
+{
+ GtkWidget *preview;
+ GtkWidget *frame;
+
+ frame = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_container_border_width (GTK_CONTAINER(frame),0);
+ gtk_widget_show(frame);
+
+ preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
+ :GTK_PREVIEW_GRAYSCALE);
+ gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
+ gtk_container_add(GTK_CONTAINER(frame),preview);
+ gtk_widget_show(preview);
+
+ my_preview_rendering_function(preview);
+ return frame;
+}
+
+That's my basic preview. This routine returns the "parent" frame so you can
+place it somewhere else in your interface. Of course, you can pass the
+parent frame to this routine as a parameter. In many situations, however,
+the contents of the preview are changed continually by your application. In
+this case you may want to pass a pointer to the preview to a
+"create_a_preview()" and thus have control of it later.
+
+One more important note that may one day save you a lot of time. Sometimes
+it is desirable to label you preview. For example, you may label the preview
+containing the original image as "Original" and the one containing the
+modified image as "Less Original". It might occure to you to pack the
+preview along with the appropriate label into a vbox. The unexpected caveat
+is that if the label is wider than the preview (which may happen for a
+variety of reasons unforseeable to you, from the dynamic decision on the
+size of the preview to the size of the font) the frame expands and no longer
+fits tightly over the preview. The same problem can probably arise in other
+situations as well.
+
+ [Image]
+
+The solution is to place the preview and the label into a 2x1 table and by
+attaching them with the following paramters (this is one possible variations
+of course. The key is no GTK_FILL in the second attachment):
+
+gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
+ 0,
+ GTK_EXPAND|GTK_FILL,
+ 0,0);
+gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
+ GTK_EXPAND,
+ GTK_EXPAND,
+ 0,0);
And here's the result:
- [Image]
+ [Image]
+
+Misc
+
+Making a preview clickable is achieved most easily by placing it in a
+button. It also adds a nice border around the preview and you may not even
+need to place it in a frame. See the Filter Pack Simulation plug-in for an
+example.
+
+This is pretty much it as far as GTK is concerned.
+
+Filling In a Preview
+
+In order to familiarize ourselves with the basics of filling in previews,
+let's create the following pattern (contrived by trial and error):
+
+ [Image]
+
+void
+my_preview_rendering_function(GtkWidget *preview)
+{
+#define SIZE 100
+#define HALF (SIZE/2)
+
+ guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
+ gint i, j; /* Coordinates */
+ double r, alpha, x, y;
+
+ if (preview==NULL) return; /* I usually add this when I want */
+ /* to avoid silly crashes. You */
+ /* should probably make sure that */
+ /* everything has been nicely */
+ /* initialized! */
+ for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
+ /* glib.h contains ABS(x). */
+ row[i*3+0] = sqrt(1-r)*255; /* Define Red */
+ row[i*3+1] = 128; /* Define Green */
+ row[i*3+2] = 224; /* Define Blue */
+ } /* "+0" is for alignment! */
+ else {
+ row[i*3+0] = r*255;
+ row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
+ row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
+ }
+ }
+ gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
+ /* Insert "row" into "preview" starting at the point with */
+ /* coordinates (0,j) first column, j_th row extending SIZE */
+ /* pixels to the right */
+ }
+
+ free(row); /* save some space */
+ gtk_widget_draw(preview,NULL); /* what does this do? */
+ gdk_flush(); /* or this? */
+}
+
+Non-GIMP users can have probably seen enough to do a lot of things already.
+For the GIMP users I have a few pointers to add.
+
+Image Preview
+
+It is probably wize to keep a reduced version of the image around with just
+enough pixels to fill the preview. This is done by selecting every n'th
+pixel where n is the ratio of the size of the image to the size of the
+preview. All further operations (including filling in the previews) are then
+performed on the reduced number of pixels only. The following is my
+implementation of reducing the image. (Keep in mind that I've had only basic
+C!)
+
+(UNTESTED CODE ALERT!!!)
+
+typedef struct {
+ gint width;
+ gint height;
+ gint bbp;
+ guchar *rgb;
+ guchar *mask;
+} ReducedImage;
+
+enum {
+ SELECTION_ONLY,
+ SELCTION_IN_CONTEXT,
+ ENTIRE_IMAGE
+};
+
+ReducedImage *Reduce_The_Image(GDrawable *drawable,
+ GDrawable *mask,
+ gint LongerSize,
+ gint Selection)
+{
+ /* This function reduced the image down to the the selected preview size */
+ /* The preview size is determine by LongerSize, i.e. the greater of the */
+ /* two dimentions. Works for RGB images only! */
+ gint RH, RW; /* Reduced height and reduced width */
+ gint width, height; /* Width and Height of the area being reduced */
+ gint bytes=drawable->bpp;
+ ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
+
+ guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
+ gint i, j, whichcol, whichrow, x1, x2, y1, y2;
+ GPixelRgn srcPR, srcMask;
+ gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
+ /* image. */
+
+ gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
+ width = x2-x1;
+ height = y2-y1;
+ /* If there's a SELECTION, we got its bounds!)
+
+ if (width != drawable->width && height != drawable->height)
+ NoSelectionMade=FALSE;
+ /* Become aware of whether the user has made an active selection */
+ /* This will become important later, when creating a reduced mask. */
+
+ /* If we want to preview the entire image, overrule the above! */
+ /* Of course, if no selection has been made, this does nothing! */
+ if (Selection==ENTIRE_IMAGE) {
+ x1=0;
+ x2=drawable->width;
+ y1=0;
+ y2=drawable->height;
+ }
+
+ /* If we want to preview a selection with some surronding area we */
+ /* have to expand it a little bit. Consider it a bit of a riddle. */
+ if (Selection==SELECTION_IN_CONTEXT) {
+ x1=MAX(0, x1-width/2.0);
+ x2=MIN(drawable->width, x2+width/2.0);
+ y1=MAX(0, y1-height/2.0);
+ y2=MIN(drawable->height, y2+height/2.0);
+ }
+
+ /* How we can determine the width and the height of the area being */
+ /* reduced. */
+ width = x2-x1;
+ height = y2-y1;
+
+ /* The lines below determine which dimension is to be the longer */
+ /* side. The idea borrowed from the supernova plug-in. I suspect I */
+ /* could've thought of it myself, but the truth must be told. */
+ /* Plagiarism stinks! */
+ if (width>height) {
+ RW=LongerSize;
+ RH=(float) height * (float) LongerSize/ (float) width;
+ }
+ else {
+ RH=LongerSize;
+ RW=(float)width * (float) LongerSize/ (float) height;
+ }
+
+ /* The intire image is stretched into a string! */
+ tempRGB = (guchar *) malloc(RW*RH*bytes);
+ tempmask = (guchar *) malloc(RW*RH);
+
+ gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
+ gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
+
+ /* Grab enough to save a row of image and a row of mask. */
+ src_row = (guchar *) malloc (width*bytes);
+ src_mask_row = (guchar *) malloc (width);
+
+ for (i=0; i < RH; i++) {
+ whichrow=(float)i*(float)height/(float)RH;
+ gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
+ gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
+
+ for (j=0; j < RW; j++) {
+ whichcol=(float)j*(float)width/(float)RW;
+
+ /* No selection made = each point is completely selected! */
+ if (NoSelectionMade)
+ tempmask[i*RW+j]=255;
+ else
+ tempmask[i*RW+j]=src_mask_row[whichcol];
+
+ /* Add the row to the one long string which now contains the image! */
+ tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
+ tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
+ tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
+
+ /* Hold on to the alpha as well */
+ if (bytes==4)
+ tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
+ }
+ }
+ temp->bpp=bytes;
+ temp->width=RW;
+ temp->height=RH;
+ temp->rgb=tempRGB;
+ temp->mask=tempmask;
+ return temp;
+}
+
+The following is a preview function which used the same ReducedImage type!
+Note that it uses fakes transparancy (if one is present by means of
+fake_transparancy which is defined as follows:
+
+gint fake_transparency(gint i, gint j)
+{
+ if ( ((i%20)- 10) * ((j%20)- 10)>0 )
+ return 64;
+ else
+ return 196;
+}
+
+Now here's the preview function:
+
+void
+my_preview_render_function(GtkWidget *preview,
+ gint changewhat,
+ gint changewhich)
+{
+ gint Inten, bytes=drawable->bpp;
+ gint i, j, k;
+ float partial;
+ gint RW=reduced->width;
+ gint RH=reduced->height;
+ guchar *row=malloc(bytes*RW);;
+
+
+ for (i=0; i < RH; i++) {
+ for (j=0; j < RW; j++) {
+
+ row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
+ row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
+ row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
+
+ if (bytes==4)
+ for (k=0; k<3; k++) {
+ float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
+ row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
+ }
+ }
+ gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
+ }
+
+ free(a);
+ gtk_widget_draw(preview,NULL);
+ gdk_flush();
+}
+
+Applicable Routines
+
+guint gtk_preview_get_type (void);
+/* No idea */
+void gtk_preview_uninit (void);
+/* No idea */
+GtkWidget* gtk_preview_new (GtkPreviewType type);
+/* Described above */
+void gtk_preview_size (GtkPreview *preview,
+ gint width,
+ gint height);
+/* Allows you to resize an existing preview. */
+/* Apparantly there's a bug in GTK which makes */
+/* this process messy. A way to clean up a mess */
+/* is to manually resize the window containing */
+/* the preview after resizing the preview. */
+
+void gtk_preview_put (GtkPreview *preview,
+ GdkWindow *window,
+ GdkGC *gc,
+ gint srcx,
+ gint srcy,
+ gint destx,
+ gint desty,
+ gint width,
+ gint height);
+/* No idea */
+
+void gtk_preview_put_row (GtkPreview *preview,
+ guchar *src,
+ guchar *dest,
+ gint x,
+ gint y,
+ gint w);
+/* No idea */
+
+void gtk_preview_draw_row (GtkPreview *preview,
+ guchar *data,
+ gint x,
+ gint y,
+ gint w);
+/* Described in the text */
+
+void gtk_preview_set_expand (GtkPreview *preview,
+ gint expand);
+/* No idea */
+
+/* No clue for any of the below but */
+/* should be standard for most widgets */
+void gtk_preview_set_gamma (double gamma);
+void gtk_preview_set_color_cube (guint nred_shades,
+ guint ngreen_shades,
+ guint nblue_shades,
+ guint ngray_shades);
+void gtk_preview_set_install_cmap (gint install_cmap);
+void gtk_preview_set_reserved (gint nreserved);
+GdkVisual* gtk_preview_get_visual (void);
+GdkColormap* gtk_preview_get_cmap (void);
+GtkPreviewInfo* gtk_preview_get_info (void);
+
+That's all, folks!
+
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>The EventBox Widget<label id="sec_The_EventBox_Widget">
+<!-- ***************************************************************** -->
+<p>
+Some gtk widgets don't have associated X windows, so they just draw on
+their parents. Because of this, they cannot recieve events
+and if they are incorrectly sized, they don't clip so you can get
+messy overwritting etc. If you require more from these widgets, the
+EventBox is for you.
+
+At first glance, the EventBox widget might appear to be totally
+useless. It draws nothing on the screen and responds to no
+events. However, it does serve a function - it provides an X window for
+its child widget. This is important as many GTK widgets do not
+have an associated X window. Not having an X window saves memory and
+improves performance, but also has some drawbacks. A widget without an
+X window cannot receive events, and does not perform any clipping on
+it's contents. Although the name <em/EventBox/ emphasizes the
+event-handling function, the widget can also be used for clipping.
+(And more ... see the example below.)
+
+To create a new EventBox widget, use:
+
+<tscreen><verb>
+GtkWidget *gtk_event_box_new( void );
+</verb></tscreen>
+
+A child widget can then be added to this EventBox:
+
+<tscreen><verb>
+gtk_container_add( GTK_CONTAINER(event_box), widget );
+</verb></tscreen>
+
+The following example demonstrates both uses of an EventBox - a label
+is created that is clipped to a small box, and set up so that a
+mouse-click on the label causes the program to exit.
+
+<tscreen><verb>
+/* example-start eventbox eventbox.c */
+
+#include <gtk/gtk.h>
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *event_box;
+ GtkWidget *label;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
+
+ /* Create an EventBox and add it to our toplevel window */
+
+ event_box = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER(window), event_box);
+ gtk_widget_show (event_box);
+
+ /* Create a long label */
+
+ label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
+ gtk_container_add (GTK_CONTAINER (event_box), label);
+ gtk_widget_show (label);
+
+ /* Clip it short. */
+ gtk_widget_set_usize (label, 110, 20);
+
+ /* And bind an action to it */
+ gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
+ gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ /* Yet one more thing you need an X window for ... */
+
+ gtk_widget_realize (event_box);
+ gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes">
+<!-- ***************************************************************** -->
+<p>
+This describes the functions used to operate on widgets. These can be used
+to set style, padding, size etc.
+
+(Maybe I should make a whole section on accelerators.)
+
+<tscreen><verb>
+void gtk_widget_install_accelerator( GtkWidget *widget,
+ GtkAcceleratorTable *table,
+ gchar *signal_name,
+ gchar key,
+ guint8 modifiers );
+
+void gtk_widget_remove_accelerator ( GtkWidget *widget,
+ GtkAcceleratorTable *table,
+ gchar *signal_name);
+
+void gtk_widget_activate( GtkWidget *widget );
+
+void gtk_widget_set_name( GtkWidget *widget,
+ gchar *name );
+
+gchar *gtk_widget_get_name( GtkWidget *widget );
+
+void gtk_widget_set_sensitive( GtkWidget *widget,
+ gint sensitive );
+
+void gtk_widget_set_style( GtkWidget *widget,
+ GtkStyle *style );
+
+GtkStyle *gtk_widget_get_style( GtkWidget *widget );
+
+GtkStyle *gtk_widget_get_default_style( void );
+
+void gtk_widget_set_uposition( GtkWidget *widget,
+ gint x,
+ gint y );
+
+void gtk_widget_set_usize( GtkWidget *widget,
+ gint width,
+ gint height );
+
+void gtk_widget_grab_focus( GtkWidget *widget );
+
+void gtk_widget_show( GtkWidget *widget );
+
+void gtk_widget_hide( GtkWidget *widget );
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts">
+<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Timeouts
+<p>
+You may be wondering how you make GTK do useful work when in gtk_main.
+Well, you have several options. Using the following functions you can
+create a timeout function that will be called every "interval"
+milliseconds.
+
+<tscreen><verb>
+gint gtk_timeout_add( guint32 interval,
+ GtkFunction function,
+ gpointer data );
+</verb></tscreen>
+
+The first argument is the number of milliseconds between calls to your
+function. The second argument is the function you wish to have called, and
+the third, the data passed to this callback function. The return value is
+an integer "tag" which may be used to stop the timeout by calling:
+
+<tscreen><verb>
+void gtk_timeout_remove( gint tag );
+</verb></tscreen>
+
+You may also stop the timeout function by returning zero or FALSE from
+your callback function. Obviously this means if you want your function to
+continue to be called, it should return a non-zero value, ie TRUE.
+
+The declaration of your callback should look something like this:
+
+<tscreen><verb>
+gint timeout_callback( gpointer data );
+</verb></tscreen>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Monitoring IO
+<p>
+Another nifty feature of GTK, is the ability to have it check for data on a
+file descriptor for you (as returned by open(2) or socket(2)). This is
+especially useful for networking applications. The function:
+
+<tscreen><verb>
+gint gdk_input_add( gint source,
+ GdkInputCondition condition,
+ GdkInputFunction function,
+ gpointer data );
+</verb></tscreen>
+
+Where the first argument is the file descriptor you wish to have watched,
+and the second specifies what you want GDK to look for. This may be one of:
+
+<itemize>
+<item>GDK_INPUT_READ - Call your function when there is data ready for
+reading on your file descriptor.
+
+<item>GDK_INPUT_WRITE - Call your function when the file descriptor is
+ready for writing.
+</itemize>
+
+As I'm sure you've figured out already, the third argument is the function
+you wish to have called when the above conditions are satisfied, and the
+fourth is the data to pass to this function.
+
+The return value is a tag that may be used to stop GDK from monitoring this
+file descriptor using the following function.
+
+<tscreen><verb>
+void gdk_input_remove( gint tag );
+</verb></tscreen>
+
+The callback function should be declared as:
+
+<tscreen><verb>
+void input_callback( gpointer data,
+ gint source,
+ GdkInputCondition condition );
+</verb></tscreen>
+
+Where <tt/source/ and <tt/condition/ are as specified above.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Idle Functions
+<p>
+<!-- Need to check on idle priorities - TRG -->
+What if you have a function you want called when nothing else is
+happening ?
+
+<tscreen><verb>
+gint gtk_idle_add( GtkFunction function,
+ gpointer data );
+</verb></tscreen>
+
+This causes GTK to call the specified function whenever nothing else is
+happening.
+
+<tscreen><verb>
+void gtk_idle_remove( gint tag );
+</verb></tscreen>
+
+I won't explain the meaning of the arguments as they follow very much like
+the ones above. The function pointed to by the first argument to
+gtk_idle_add will be called whenever the opportunity arises. As with the
+others, returning FALSE will stop the idle function from being called.
+
+<!-- ***************************************************************** -->
+<sect>Managing Selections
+<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
+<p>
+One type of interprocess communication supported by GTK is
+<em>selections</em>. A selection identifies a chunk of data, for
+instance, a portion of text, selected by the user in some fashion, for
+instance, by dragging with the mouse. Only one application on a
+display, (the <em>owner</em> can own a particular selection at one
+time, so when a selection is claimed by one application, the previous
+owner must indicate to the user that selection has been
+relinquished. Other applications can request the contents of a
+selection in different forms, called <em>targets</em>. There can be
+any number of selections, but most X applications only handle one, the
+<em>primary selection</em>.
+
+In most cases, it isn't necessary for a GTK application to deal with
+selections itself. The standard widgets, such as the Entry widget,
+already have the capability to claim the selection when appropriate
+(e.g., when the user drags over text), and to retrieve the contents of
+the selection owned by another widget, or another application (e.g.,
+when the user clicks the second mouse button). However, there may be
+cases in which you want to give other widgets the ability to supply
+the selection, or you wish to retrieve targets not supported by
+default.
-Misc
+A fundamental concept needed to understand selection handling is that
+of the <em>atom</em>. An atom is an integer that uniquely identifies a
+string (on a certain display). Certain atoms are predefined by the X
+server, and in some cases there are constants in <tt>gtk.h</tt>
+corresponding to these atoms. For instance the constant
+<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY".
+In other cases, you should use the functions
+<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string,
+and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both
+selections and targets are identifed by atoms.
-Making a preview clickable is achieved most easily by placing it in a
-button. It also adds a nice border around the preview and you may not even
-need to place it in a frame. See the Filter Pack Simulation plug-in for an
-example.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Retrieving the selection
+<p>
+Retrieving the selection is an asynchronous process. To start the
+process, you call:
-This is pretty much it as far as GTK is concerned.
+<tscreen><verb>
+gint gtk_selection_convert( GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target,
+ guint32 time );
+</verb</tscreen>
-Filling In a Preview
+This <em>converts</em> the selection into the form specified by
+<tt/target/. If at all possible, the time field should be the time
+from the event that triggered the selection. This helps make sure that
+events occur in the order that the user requested them. However, if it
+is not available (for instance, if the conversion was triggered by
+a "clicked" signal), then you can use the constant
+<tt>GDK_CURRENT_TIME</tt>.
-In order to familiarize ourselves with the basics of filling in previews,
-let's create the following pattern (contrived by trial and error):
+When the selection owner responds to the request, a
+"selection_received" signal is sent to your application. The handler
+for this signal receives a pointer to a <tt>GtkSelectionData</tt>
+structure, which is defined as:
- [Image]
+<tscreen><verb>
+struct _GtkSelectionData
+{
+ GdkAtom selection;
+ GdkAtom target;
+ GdkAtom type;
+ gint format;
+ guchar *data;
+ gint length;
+};
+</verb></tscreen>
+
+<tt>selection</tt> and <tt>target</tt> are the values you gave in your
+<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that
+identifies the type of data returned by the selection owner. Some
+possible values are "STRING", a string of latin-1 characters, "ATOM",
+a series of atoms, "INTEGER", an integer, etc. Most targets can only
+return one type. <tt/format/ gives the length of the units (for
+instance characters) in bits. Usually, you don't care about this when
+receiving data. <tt>data</tt> is a pointer to the returned data, and
+<tt>length</tt> gives the length of the returned data, in bytes. If
+<tt>length</tt> is negative, then an error occurred and the selection
+could not be retrieved. This might happen if no application owned the
+selection, or if you requested a target that the application didn't
+support. The buffer is actually guaranteed to be one byte longer than
+<tt>length</tt>; the extra byte will always be zero, so it isn't
+necessary to make a copy of strings just to null terminate them.
+
+In the following example, we retrieve the special target "TARGETS",
+which is a list of all targets into which the selection can be
+converted.
+
+<tscreen><verb>
+/* example-start selection gettargets.c */
+
+#include <gtk/gtk.h>
+void selection_received (GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data);
+
+/* Signal handler invoked when user clicks on the "Get Targets" button */
void
-my_preview_rendering_function(GtkWidget *preview)
+get_targets (GtkWidget *widget, gpointer data)
{
-#define SIZE 100
-#define HALF (SIZE/2)
+ static GdkAtom targets_atom = GDK_NONE;
- guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
- gint i, j; /* Coordinates */
- double r, alpha, x, y;
+ /* Get the atom corresonding to the string "TARGETS" */
+ if (targets_atom == GDK_NONE)
+ targets_atom = gdk_atom_intern ("TARGETS", FALSE);
- if (preview==NULL) return; /* I usually add this when I want */
- /* to avoid silly crashes. You */
- /* should probably make sure that */
- /* everything has been nicely */
- /* initialized! */
- for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
- /* glib.h contains ABS(x). */
- row[i*3+0] = sqrt(1-r)*255; /* Define Red */
- row[i*3+1] = 128; /* Define Green */
- row[i*3+2] = 224; /* Define Blue */
- } /* "+0" is for alignment! */
- else {
- row[i*3+0] = r*255;
- row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
- row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
- }
+ /* And request the "TARGETS" target for the primary selection */
+ gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
+ GDK_CURRENT_TIME);
+}
+
+/* Signal handler called when the selections owner returns the data */
+void
+selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
+ gpointer data)
+{
+ GdkAtom *atoms;
+ GList *item_list;
+ int i;
+
+ /* **** IMPORTANT **** Check to see if retrieval succeeded */
+ if (selection_data->length < 0)
+ {
+ g_print ("Selection retrieval failed\n");
+ return;
}
- gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
- /* Insert "row" into "preview" starting at the point with */
- /* coordinates (0,j) first column, j_th row extending SIZE */
- /* pixels to the right */
- }
+ /* Make sure we got the data in the expected form */
+ if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
+ {
+ g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
+ return;
+ }
+
+ /* Print out the atoms we received */
+ atoms = (GdkAtom *)selection_data->data;
- free(row); /* save some space */
- gtk_widget_draw(preview,NULL); /* what does this do? */
- gdk_flush(); /* or this? */
+ item_list = NULL;
+ for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
+ {
+ char *name;
+ name = gdk_atom_name (atoms[i]);
+ if (name != NULL)
+ g_print ("%s\n",name);
+ else
+ g_print ("(bad atom)\n");
+ }
+
+ return;
}
-Non-GIMP users can have probably seen enough to do a lot of things already.
-For the GIMP users I have a few pointers to add.
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *button;
+
+ gtk_init (&argc, &argv);
-Image Preview
+ /* Create the toplevel window */
-It is probably wize to keep a reduced version of the image around with just
-enough pixels to fill the preview. This is done by selecting every n'th
-pixel where n is the ratio of the size of the image to the size of the
-preview. All further operations (including filling in the previews) are then
-performed on the reduced number of pixels only. The following is my
-implementation of reducing the image. (Keep in mind that I've had only basic
-C!)
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-(UNTESTED CODE ALERT!!!)
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
-typedef struct {
- gint width;
- gint height;
- gint bbp;
- guchar *rgb;
- guchar *mask;
-} ReducedImage;
+ /* Create a button the user can click to get targets */
-enum {
- SELECTION_ONLY,
- SELCTION_IN_CONTEXT,
- ENTIRE_IMAGE
-};
+ button = gtk_button_new_with_label ("Get Targets");
+ gtk_container_add (GTK_CONTAINER (window), button);
-ReducedImage *Reduce_The_Image(GDrawable *drawable,
- GDrawable *mask,
- gint LongerSize,
- gint Selection)
-{
- /* This function reduced the image down to the the selected preview size */
- /* The preview size is determine by LongerSize, i.e. the greater of the */
- /* two dimentions. Works for RGB images only! */
- gint RH, RW; /* Reduced height and reduced width */
- gint width, height; /* Width and Height of the area being reduced */
- gint bytes=drawable->bpp;
- ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
+ gtk_signal_connect (GTK_OBJECT(button), "clicked",
+ GTK_SIGNAL_FUNC (get_targets), NULL);
+ gtk_signal_connect (GTK_OBJECT(button), "selection_received",
+ GTK_SIGNAL_FUNC (selection_received), NULL);
- guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
- gint i, j, whichcol, whichrow, x1, x2, y1, y2;
- GPixelRgn srcPR, srcMask;
- gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
- /* image. */
+ gtk_widget_show (button);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
- gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
- width = x2-x1;
- height = y2-y1;
- /* If there's a SELECTION, we got its bounds!)
+<!-- ----------------------------------------------------------------- -->
+<sect1> Supplying the selection
+<p>
+Supplying the selection is a bit more complicated. You must register
+handlers that will be called when your selection is requested. For
+each selection/target pair you will handle, you make a call to:
- if (width != drawable->width && height != drawable->height)
- NoSelectionMade=FALSE;
- /* Become aware of whether the user has made an active selection */
- /* This will become important later, when creating a reduced mask. */
+<tscreen><verb>
+void gtk_selection_add_handler( GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target,
+ GtkSelectionFunction function,
+ GtkRemoveFunction remove_func,
+ gpointer data );
+</verb></tscreen>
- /* If we want to preview the entire image, overrule the above! */
- /* Of course, if no selection has been made, this does nothing! */
- if (Selection==ENTIRE_IMAGE) {
- x1=0;
- x2=drawable->width;
- y1=0;
- y2=drawable->height;
- }
+<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
+this handler will manage. <tt/remove_func/, if not
+NULL, will be called when the signal handler is removed. This is
+useful, for instance, for interpreted languages which need to
+keep track of a reference count for <tt/data/.
- /* If we want to preview a selection with some surronding area we */
- /* have to expand it a little bit. Consider it a bit of a riddle. */
- if (Selection==SELECTION_IN_CONTEXT) {
- x1=MAX(0, x1-width/2.0);
- x2=MIN(drawable->width, x2+width/2.0);
- y1=MAX(0, y1-height/2.0);
- y2=MIN(drawable->height, y2+height/2.0);
- }
+The callback function has the signature:
- /* How we can determine the width and the height of the area being */
- /* reduced. */
- width = x2-x1;
- height = y2-y1;
+<tscreen><verb>
+typedef void (*GtkSelectionFunction)( GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data );
- /* The lines below determine which dimension is to be the longer */
- /* side. The idea borrowed from the supernova plug-in. I suspect I */
- /* could've thought of it myself, but the truth must be told. */
- /* Plagiarism stinks! */
- if (width>height) {
- RW=LongerSize;
- RH=(float) height * (float) LongerSize/ (float) width;
- }
- else {
- RH=LongerSize;
- RW=(float)width * (float) LongerSize/ (float) height;
- }
+</verb></tscreen>
- /* The intire image is stretched into a string! */
- tempRGB = (guchar *) malloc(RW*RH*bytes);
- tempmask = (guchar *) malloc(RW*RH);
+The GtkSelectionData is the same as above, but this time, we're
+responsible for filling in the fields <tt/type/, <tt/format/,
+<tt/data/, and <tt/length/. (The <tt/format/ field is actually
+important here - the X server uses it to figure out whether the data
+needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a
+character - or 32 - <em/i.e./ a. integer.) This is done by calling the
+function:
- gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
- gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
+<tscreen><verb>
+void gtk_selection_data_set( GtkSelectionData *selection_data,
+ GdkAtom type,
+ gint format,
+ guchar *data,
+ gint length );
+</verb></tscreen>
- /* Grab enough to save a row of image and a row of mask. */
- src_row = (guchar *) malloc (width*bytes);
- src_mask_row = (guchar *) malloc (width);
+This function takes care of properly making a copy of the data so that
+you don't have to worry about keeping it around. (You should not fill
+in the fields of the GtkSelectionData structure by hand.)
- for (i=0; i < RH; i++) {
- whichrow=(float)i*(float)height/(float)RH;
- gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
- gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
+When prompted by the user, you claim ownership of the selection by
+calling:
- for (j=0; j < RW; j++) {
- whichcol=(float)j*(float)width/(float)RW;
+<tscreen><verb>
+gint gtk_selection_owner_set( GtkWidget *widget,
+ GdkAtom selection,
+ guint32 time );
+</verb></tscreen>
- /* No selection made = each point is completely selected! */
- if (NoSelectionMade)
- tempmask[i*RW+j]=255;
- else
- tempmask[i*RW+j]=src_mask_row[whichcol];
+If another application claims ownership of the selection, you will
+receive a "selection_clear_event".
- /* Add the row to the one long string which now contains the image! */
- tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
- tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
- tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
+As an example of supplying the selection, the following program adds
+selection functionality to a toggle button. When the toggle button is
+depressed, the program claims the primary selection. The only target
+supported (aside from certain targets like "TARGETS" supplied by GTK
+itself), is the "STRING" target. When this target is requested, a
+string representation of the time is returned.
- /* Hold on to the alpha as well */
- if (bytes==4)
- tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
- }
- }
- temp->bpp=bytes;
- temp->width=RW;
- temp->height=RH;
- temp->rgb=tempRGB;
- temp->mask=tempmask;
- return temp;
-}
+<tscreen><verb>
+/* example-start selection setselection.c */
-The following is a preview function which used the same ReducedImage type!
-Note that it uses fakes transparancy (if one is present by means of
-fake_transparancy which is defined as follows:
+#include <gtk/gtk.h>
+#include <time.h>
-gint fake_transparency(gint i, gint j)
+/* Callback when the user toggles the selection */
+void
+selection_toggled (GtkWidget *widget, gint *have_selection)
{
- if ( ((i%20)- 10) * ((j%20)- 10)>0 )
- return 64;
+ if (GTK_TOGGLE_BUTTON(widget)->active)
+ {
+ *have_selection = gtk_selection_owner_set (widget,
+ GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME);
+ /* if claiming the selection failed, we return the button to
+ the out state */
+ if (!*have_selection)
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
+ }
else
- return 196;
+ {
+ if (*have_selection)
+ {
+ /* Before clearing the selection by setting the owner to NULL,
+ we check if we are the actual owner */
+ if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
+ gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME);
+ *have_selection = FALSE;
+ }
+ }
}
-Now here's the preview function:
-
-void
-my_preview_render_function(GtkWidget *preview,
- gint changewhat,
- gint changewhich)
+/* Called when another application claims the selection */
+gint
+selection_clear (GtkWidget *widget, GdkEventSelection *event,
+ gint *have_selection)
{
- gint Inten, bytes=drawable->bpp;
- gint i, j, k;
- float partial;
- gint RW=reduced->width;
- gint RH=reduced->height;
- guchar *row=malloc(bytes*RW);;
-
+ *have_selection = FALSE;
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
- for (i=0; i < RH; i++) {
- for (j=0; j < RW; j++) {
+ return TRUE;
+}
- row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
- row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
- row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
+/* Supplies the current time as the selection. */
+void
+selection_handle (GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ gchar *timestr;
+ time_t current_time;
- if (bytes==4)
- for (k=0; k<3; k++) {
- float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
- row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
- }
- }
- gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
- }
+ current_time = time (NULL);
+ timestr = asctime (localtime(&current_time));
+ /* When we return a single string, it should not be null terminated.
+ That will be done for us */
- free(a);
- gtk_widget_draw(preview,NULL);
- gdk_flush();
+ gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
+ 8, timestr, strlen(timestr));
}
-Applicable Routines
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
-guint gtk_preview_get_type (void);
-/* No idea */
-void gtk_preview_uninit (void);
-/* No idea */
-GtkWidget* gtk_preview_new (GtkPreviewType type);
-/* Described above */
-void gtk_preview_size (GtkPreview *preview,
- gint width,
- gint height);
-/* Allows you to resize an existing preview. */
-/* Apparantly there's a bug in GTK which makes */
-/* this process messy. A way to clean up a mess */
-/* is to manually resize the window containing */
-/* the preview after resizing the preview. */
+ GtkWidget *selection_button;
-void gtk_preview_put (GtkPreview *preview,
- GdkWindow *window,
- GdkGC *gc,
- gint srcx,
- gint srcy,
- gint destx,
- gint desty,
- gint width,
- gint height);
-/* No idea */
+ static int have_selection = FALSE;
+
+ gtk_init (&argc, &argv);
-void gtk_preview_put_row (GtkPreview *preview,
- guchar *src,
- guchar *dest,
- gint x,
- gint y,
- gint w);
-/* No idea */
+ /* Create the toplevel window */
-void gtk_preview_draw_row (GtkPreview *preview,
- guchar *data,
- gint x,
- gint y,
- gint w);
-/* Described in the text */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-void gtk_preview_set_expand (GtkPreview *preview,
- gint expand);
-/* No idea */
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ /* Create a toggle button to act as the selection */
-/* No clue for any of the below but */
-/* should be standard for most widgets */
-void gtk_preview_set_gamma (double gamma);
-void gtk_preview_set_color_cube (guint nred_shades,
- guint ngreen_shades,
- guint nblue_shades,
- guint ngray_shades);
-void gtk_preview_set_install_cmap (gint install_cmap);
-void gtk_preview_set_reserved (gint nreserved);
-GdkVisual* gtk_preview_get_visual (void);
-GdkColormap* gtk_preview_get_cmap (void);
-GtkPreviewInfo* gtk_preview_get_info (void);
+ selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
+ gtk_container_add (GTK_CONTAINER (window), selection_button);
+ gtk_widget_show (selection_button);
-That's all, folks!
+ gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
+ GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
+ gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
+ GTK_SIGNAL_FUNC (selection_clear), &have_selection);
+
+ gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING,
+ selection_handle, NULL);
+ gtk_widget_show (selection_button);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
</verb></tscreen>
+
<!-- ***************************************************************** -->
-<sect>The EventBox Widget<label id="sec_The_EventBox_Widget">
+<sect>glib<label id="sec_glib">
<!-- ***************************************************************** -->
-<p>
-Some gtk widgets don't have associated X windows, so they just draw on
-their parents. Because of this, they cannot recieve events
-and if they are incorrectly sized, they don't clip so you can get
-messy overwritting etc. If you require more from these widgets, the
-EventBox is for you.
-
-At first glance, the EventBox widget might appear to be totally
-useless. It draws nothing on the screen and responds to no
-events. However, it does serve a function - it provides an X window for
-its child widget. This is important as many GTK widgets do not
-have an associated X window. Not having an X window saves memory and
-improves performance, but also has some drawbacks. A widget without an
-X window cannot receive events, and does not perform any clipping on
-it's contents. Although the name <em/EventBox/ emphasizes the
-event-handling function, the widget can also be used for clipping.
-(And more ... see the example below.)
+<p>
+glib provides many useful functions and definitions available for use
+when creating GDK and GTK applications. I will list them all here with
+a brief explanation. Many are duplicates of standard libc functions so
+I won't go into detail on those. This is mostly to be used as a reference,
+so you know what is available for use.
-To create a new EventBox widget, use:
+<!-- ----------------------------------------------------------------- -->
+<sect1>Definitions
+<p>
+Definitions for the extremes of many of the standard types are:
<tscreen><verb>
-GtkWidget *gtk_event_box_new( void );
+G_MINFLOAT
+G_MAXFLOAT
+G_MINDOUBLE
+G_MAXDOUBLE
+G_MINSHORT
+G_MAXSHORT
+G_MININT
+G_MAXINT
+G_MINLONG
+G_MAXLONG
</verb></tscreen>
-A child widget can then be added to this EventBox:
+Also, the following typedefs. The ones left unspecified are dynamically set
+depending on the architecture. Remember to avoid counting on the size of a
+pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4
+on Intel.
<tscreen><verb>
-gtk_container_add( GTK_CONTAINER(event_box), widget );
-</verb></tscreen>
+char gchar;
+short gshort;
+long glong;
+int gint;
+char gboolean;
-The following example demonstrates both uses of an EventBox - a label
-is created that is clipped to a small box, and set up so that a
-mouse-click on the label causes the program to exit.
+unsigned char guchar;
+unsigned short gushort;
+unsigned long gulong;
+unsigned int guint;
-<tscreen><verb>
-/* example-start eventbox eventbox.c */
+float gfloat;
+double gdouble;
+long double gldouble;
-#include <gtk/gtk.h>
+void* gpointer;
-int
-main (int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *event_box;
- GtkWidget *label;
-
- gtk_init (&argc, &argv);
-
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
-
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- gtk_container_border_width (GTK_CONTAINER (window), 10);
-
- /* Create an EventBox and add it to our toplevel window */
-
- event_box = gtk_event_box_new ();
- gtk_container_add (GTK_CONTAINER(window), event_box);
- gtk_widget_show (event_box);
-
- /* Create a long label */
-
- label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
- gtk_container_add (GTK_CONTAINER (event_box), label);
- gtk_widget_show (label);
-
- /* Clip it short. */
- gtk_widget_set_usize (label, 110, 20);
-
- /* And bind an action to it */
- gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
- gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- /* Yet one more thing you need an X window for ... */
-
- gtk_widget_realize (event_box);
- gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
-
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+gint8
+guint8
+gint16
+guint16
+gint32
+guint32
</verb></tscreen>
-<!-- ***************************************************************** -->
-<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes">
-<!-- ***************************************************************** -->
+<!-- ----------------------------------------------------------------- -->
+<sect1>Doubly Linked Lists
<p>
-This describes the functions used to operate on widgets. These can be used
-to set style, padding, size etc.
-
-(Maybe I should make a whole section on accelerators.)
+The following functions are used to create, manage, and destroy doubly
+linked lists. I assume you know what linked lists are, as it is beyond the scope
+of this document to explain them. Of course, it's not required that you
+know these for general use of GTK, but they are nice to know.
<tscreen><verb>
-void gtk_widget_install_accelerator( GtkWidget *widget,
- GtkAcceleratorTable *table,
- gchar *signal_name,
- gchar key,
- guint8 modifiers );
+GList *g_list_alloc( void );
-void gtk_widget_remove_accelerator ( GtkWidget *widget,
- GtkAcceleratorTable *table,
- gchar *signal_name);
+void g_list_free( GList *list );
-void gtk_widget_activate( GtkWidget *widget );
+void g_list_free_1( GList *list );
-void gtk_widget_set_name( GtkWidget *widget,
- gchar *name );
+GList *g_list_append( GList *list,
+ gpointer data );
+
+GList *g_list_prepend( GList *list,
+ gpointer data );
+
+GList *g_list_insert( GList *list,
+ gpointer data,
+ gint position );
-gchar *gtk_widget_get_name( GtkWidget *widget );
+GList *g_list_remove( GList *list,
+ gpointer data );
+
+GList *g_list_remove_link( GList *list,
+ GList *link );
-void gtk_widget_set_sensitive( GtkWidget *widget,
- gint sensitive );
+GList *g_list_reverse( GList *list );
+
+GList *g_list_nth( GList *list,
+ gint n );
+
+GList *g_list_find( GList *list,
+ gpointer data );
+
+GList *g_list_last( GList *list );
+
+GList *g_list_first( GList *list );
+
+gint g_list_length( GList *list );
+
+void g_list_foreach( GList *list,
+ GFunc func,
+ gpointer user_data );
+</verb></tscreen>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Singly Linked Lists
+<p>
+Many of the above functions for singly linked lists are identical to the
+above. Here is a complete list:
+<tscreen><verb>
+GSList *g_slist_alloc( void );
+
+void g_slist_free( GSList *list );
+
+void g_slist_free_1( GSList *list );
+
+GSList *g_slist_append( GSList *list,
+ gpointer data );
+
+GSList *g_slist_prepend( GSList *list,
+ gpointer data );
+
+GSList *g_slist_insert( GSList *list,
+ gpointer data,
+ gint position );
+
+GSList *g_slist_remove( GSList *list,
+ gpointer data );
+
+GSList *g_slist_remove_link( GSList *list,
+ GSList *link );
+
+GSList *g_slist_reverse( GSList *list );
+
+GSList *g_slist_nth( GSList *list,
+ gint n );
+
+GSList *g_slist_find( GSList *list,
+ gpointer data );
+
+GSList *g_slist_last( GSList *list );
-void gtk_widget_set_style( GtkWidget *widget,
- GtkStyle *style );
-
-GtkStyle *gtk_widget_get_style( GtkWidget *widget );
+gint g_slist_length( GSList *list );
-GtkStyle *gtk_widget_get_default_style( void );
+void g_slist_foreach( GSList *list,
+ GFunc func,
+ gpointer user_data );
+
+</verb></tscreen>
-void gtk_widget_set_uposition( GtkWidget *widget,
- gint x,
- gint y );
+<!-- ----------------------------------------------------------------- -->
+<sect1>Memory Management
+<p>
+<tscreen><verb>
+gpointer g_malloc( gulong size );
+</verb></tscreen>
-void gtk_widget_set_usize( GtkWidget *widget,
- gint width,
- gint height );
+This is a replacement for malloc(). You do not need to check the return
+vaule as it is done for you in this function.
-void gtk_widget_grab_focus( GtkWidget *widget );
+<tscreen><verb>
+gpointer g_malloc0( gulong size );
+</verb></tscreen>
-void gtk_widget_show( GtkWidget *widget );
+Same as above, but zeroes the memory before returning a pointer to it.
-void gtk_widget_hide( GtkWidget *widget );
+<tscreen><verb>
+gpointer g_realloc( gpointer mem,
+ gulong size );
</verb></tscreen>
-<!-- ***************************************************************** -->
-<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts">
-<!-- ***************************************************************** -->
-
-<!-- ----------------------------------------------------------------- -->
-<sect1>Timeouts
-<p>
-You may be wondering how you make GTK do useful work when in gtk_main.
-Well, you have several options. Using the following functions you can
-create a timeout function that will be called every "interval"
-milliseconds.
+Relocates "size" bytes of memory starting at "mem". Obviously, the
+memory should have been previously allocated.
<tscreen><verb>
-gint gtk_timeout_add( guint32 interval,
- GtkFunction function,
- gpointer data );
+void g_free( gpointer mem );
</verb></tscreen>
-The first argument is the number of milliseconds between calls to your
-function. The second argument is the function you wish to have called, and
-the third, the data passed to this callback function. The return value is
-an integer "tag" which may be used to stop the timeout by calling:
+Frees memory. Easy one.
<tscreen><verb>
-void gtk_timeout_remove( gint tag );
+void g_mem_profile( void );
</verb></tscreen>
-You may also stop the timeout function by returning zero or FALSE from
-your callback function. Obviously this means if you want your function to
-continue to be called, it should return a non-zero value, ie TRUE.
-
-The declaration of your callback should look something like this:
+Dumps a profile of used memory, but requries that you add #define
+MEM_PROFILE to the top of glib/gmem.c and re-make and make install.
<tscreen><verb>
-gint timeout_callback( gpointer data );
+void g_mem_check( gpointer mem );
</verb></tscreen>
+Checks that a memory location is valid. Requires you add #define
+MEM_CHECK to the top of gmem.c and re-make and make install.
+
<!-- ----------------------------------------------------------------- -->
-<sect1>Monitoring IO
+<sect1>Timers
<p>
-Another nifty feature of GTK, is the ability to have it check for data on a
-file descriptor for you (as returned by open(2) or socket(2)). This is
-especially useful for networking applications. The function:
+Timer functions..
<tscreen><verb>
-gint gdk_input_add( gint source,
- GdkInputCondition condition,
- GdkInputFunction function,
- gpointer data );
-</verb></tscreen>
-
-Where the first argument is the file descriptor you wish to have watched,
-and the second specifies what you want GDK to look for. This may be one of:
+GTimer *g_timer_new( void );
-<itemize>
-<item>GDK_INPUT_READ - Call your function when there is data ready for
-reading on your file descriptor.
+void g_timer_destroy( GTimer *timer );
-<item>GDK_INPUT_WRITE - Call your function when the file descriptor is
-ready for writing.
-</itemize>
+void g_timer_start( GTimer *timer );
-As I'm sure you've figured out already, the third argument is the function
-you wish to have called when the above conditions are satisfied, and the
-fourth is the data to pass to this function.
+void g_timer_stop( GTimer *timer );
-The return value is a tag that may be used to stop GDK from monitoring this
-file descriptor using the following function.
+void g_timer_reset( GTimer *timer );
-<tscreen><verb>
-void gdk_input_remove( gint tag );
-</verb></tscreen>
+gdouble g_timer_elapsed( GTimer *timer,
+ gulong *microseconds );
+</verb></tscreen>
-The callback function should be declared as:
+<!-- ----------------------------------------------------------------- -->
+<sect1>String Handling
+<p>
+A whole mess of string handling functions. They all look very interesting, and
+probably better for many purposes than the standard C string functions, but
+require documentation.
<tscreen><verb>
-void input_callback( gpointer data,
- gint source,
- GdkInputCondition condition );
-</verb></tscreen>
+GString *g_string_new( gchar *init );
-Where <tt/source/ and <tt/condition/ are as specified above.
+void g_string_free( GString *string,
+ gint free_segment );
+
+GString *g_string_assign( GString *lval,
+ gchar *rval );
+
+GString *g_string_truncate( GString *string,
+ gint len );
+
+GString *g_string_append( GString *string,
+ gchar *val );
+
+GString *g_string_append_c( GString *string,
+ gchar c );
+
+GString *g_string_prepend( GString *string,
+ gchar *val );
+
+GString *g_string_prepend_c( GString *string,
+ gchar c );
+
+void g_string_sprintf( GString *string,
+ gchar *fmt,
+ ...);
+
+void g_string_sprintfa ( GString *string,
+ gchar *fmt,
+ ... );
+</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect1>Idle Functions
+<sect1>Utility and Error Functions
<p>
-<!-- Need to check on idle priorities - TRG -->
-What if you have a function you want called when nothing else is
-happening ?
-
<tscreen><verb>
-gint gtk_idle_add( GtkFunction function,
- gpointer data );
+gchar *g_strdup( const gchar *str );
</verb></tscreen>
-This causes GTK to call the specified function whenever nothing else is
-happening.
+Replacement strdup function. Copies the original strings contents to
+newly allocated memory, and returns a pointer to it.
<tscreen><verb>
-void gtk_idle_remove( gint tag );
+gchar *g_strerror( gint errnum );
</verb></tscreen>
-I won't explain the meaning of the arguments as they follow very much like
-the ones above. The function pointed to by the first argument to
-gtk_idle_add will be called whenever the opportunity arises. As with the
-others, returning FALSE will stop the idle function from being called.
+I recommend using this for all error messages. It's much nicer, and more
+portable than perror() or others. The output is usually of the form:
-<!-- ***************************************************************** -->
-<sect>Managing Selections
-<!-- ***************************************************************** -->
+<tscreen><verb>
+program name:function that failed:file or further description:strerror
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
-<p>
-One type of interprocess communication supported by GTK is
-<em>selections</em>. A selection identifies a chunk of data, for
-instance, a portion of text, selected by the user in some fashion, for
-instance, by dragging with the mouse. Only one application on a
-display, (the <em>owner</em> can own a particular selection at one
-time, so when a selection is claimed by one application, the previous
-owner must indicate to the user that selection has been
-relinquished. Other applications can request the contents of a
-selection in different forms, called <em>targets</em>. There can be
-any number of selections, but most X applications only handle one, the
-<em>primary selection</em>.
+Here's an example of one such call used in our hello_world program:
-In most cases, it isn't necessary for a GTK application to deal with
-selections itself. The standard widgets, such as the Entry widget,
-already have the capability to claim the selection when appropriate
-(e.g., when the user drags over text), and to retrieve the contents of
-the selection owned by another widget, or another application (e.g.,
-when the user clicks the second mouse button). However, there may be
-cases in which you want to give other widgets the ability to supply
-the selection, or you wish to retrieve targets not supported by
-default.
+<tscreen><verb>
+g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
+</verb></tscreen>
-A fundamental concept needed to understand selection handling is that
-of the <em>atom</em>. An atom is an integer that uniquely identifies a
-string (on a certain display). Certain atoms are predefined by the X
-server, and in some cases there are constants in <tt>gtk.h</tt>
-corresponding to these atoms. For instance the constant
-<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY".
-In other cases, you should use the functions
-<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string,
-and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both
-selections and targets are identifed by atoms.
+<tscreen><verb>
+void g_error( gchar *format, ... );
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Retrieving the selection
-<p>
-Retrieving the selection is an asynchronous process. To start the
-process, you call:
+Prints an error message. The format is just like printf, but it
+prepends "** ERROR **: " to your message, and exits the program.
+Use only for fatal errors.
<tscreen><verb>
-gint gtk_selection_convert( GtkWidget *widget,
- GdkAtom selection,
- GdkAtom target,
- guint32 time );
-</verb</tscreen>
-
-This <em>converts</em> the selection into the form specified by
-<tt/target/. If at all possible, the time field should be the time
-from the event that triggered the selection. This helps make sure that
-events occur in the order that the user requested them. However, if it
-is not available (for instance, if the conversion was triggered by
-a "clicked" signal), then you can use the constant
-<tt>GDK_CURRENT_TIME</tt>.
+void g_warning( gchar *format, ... );
+</verb></tscreen>
-When the selection owner responds to the request, a
-"selection_received" signal is sent to your application. The handler
-for this signal receives a pointer to a <tt>GtkSelectionData</tt>
-structure, which is defined as:
+Same as above, but prepends "** WARNING **: ", and does not exit the
+program.
<tscreen><verb>
-struct _GtkSelectionData
-{
- GdkAtom selection;
- GdkAtom target;
- GdkAtom type;
- gint format;
- guchar *data;
- gint length;
-};
+void g_message( gchar *format, ... );
+</verb></tscreen>
+
+Prints "message: " prepended to the string you pass in.
+
+<tscreen><verb>
+void g_print( gchar *format, ... );
</verb></tscreen>
-<tt>selection</tt> and <tt>target</tt> are the values you gave in your
-<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that
-identifies the type of data returned by the selection owner. Some
-possible values are "STRING", a string of latin-1 characters, "ATOM",
-a series of atoms, "INTEGER", an integer, etc. Most targets can only
-return one type. <tt/format/ gives the length of the units (for
-instance characters) in bits. Usually, you don't care about this when
-receiving data. <tt>data</tt> is a pointer to the returned data, and
-<tt>length</tt> gives the length of the returned data, in bytes. If
-<tt>length</tt> is negative, then an error occurred and the selection
-could not be retrieved. This might happen if no application owned the
-selection, or if you requested a target that the application didn't
-support. The buffer is actually guaranteed to be one byte longer than
-<tt>length</tt>; the extra byte will always be zero, so it isn't
-necessary to make a copy of strings just to null terminate them.
+Replacement for printf().
-In the following example, we retrieve the special target "TARGETS",
-which is a list of all targets into which the selection can be
-converted.
+And our last function:
<tscreen><verb>
-/* example-start selection gettargets.c */
-
-#include <gtk/gtk.h>
+gchar *g_strsignal( gint signum );
+</verb></tscreen>
-void selection_received (GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data);
+Prints out the name of the Unix system signal given the signal number.
+Useful in generic signal handling functions.
-/* Signal handler invoked when user clicks on the "Get Targets" button */
-void
-get_targets (GtkWidget *widget, gpointer data)
-{
- static GdkAtom targets_atom = GDK_NONE;
+All of the above are more or less just stolen from glib.h. If anyone cares
+to document any function, just send me an email!
- /* Get the atom corresonding to the string "TARGETS" */
- if (targets_atom == GDK_NONE)
- targets_atom = gdk_atom_intern ("TARGETS", FALSE);
+<!-- ***************************************************************** -->
+<sect>GTK's rc Files
+<!-- ***************************************************************** -->
+<p>
+GTK has it's own way of dealing with application defaults, by using rc
+files. These can be used to set the colors of just about any widget, and
+can also be used to tile pixmaps onto the background of some widgets.
- /* And request the "TARGETS" target for the primary selection */
- gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
- GDK_CURRENT_TIME);
-}
+<!-- ----------------------------------------------------------------- -->
+<sect1>Functions For rc Files
+<p>
+When your application starts, you should include a call to:
-/* Signal handler called when the selections owner returns the data */
-void
-selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
- gpointer data)
-{
- GdkAtom *atoms;
- GList *item_list;
- int i;
+<tscreen><verb>
+void gtk_rc_parse( char *filename );
+</verb></tscreen>
- /* **** IMPORTANT **** Check to see if retrieval succeeded */
- if (selection_data->length < 0)
- {
- g_print ("Selection retrieval failed\n");
- return;
- }
- /* Make sure we got the data in the expected form */
- if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
- {
- g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
- return;
- }
-
- /* Print out the atoms we received */
- atoms = (GdkAtom *)selection_data->data;
+Passing in the filename of your rc file. This will cause GTK to parse this
+file, and use the style settings for the widget types defined there.
- item_list = NULL;
- for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
- {
- char *name;
- name = gdk_atom_name (atoms[i]);
- if (name != NULL)
- g_print ("%s\n",name);
- else
- g_print ("(bad atom)\n");
- }
+If you wish to have a special set of widgets that can take on a different
+style from others, or any other logical division of widgets, use a call to:
- return;
-}
+<tscreen><verb>
+void gtk_widget_set_name( GtkWidget *widget,
+ gchar *name );
+</verb></tscreen>
-int
-main (int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *button;
-
- gtk_init (&argc, &argv);
+Passing your newly created widget as the first argument, and the name
+you wish to give it as the second. This will allow you to change the
+attributes of this widget by name through the rc file.
- /* Create the toplevel window */
+If we use a call something like this:
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+<tscreen><verb>
+button = gtk_button_new_with_label ("Special Button");
+gtk_widget_set_name (button, "special button");
+</verb></tscreen>
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
+Then this button is given the name "special button" and may be addressed by
+name in the rc file as "special button.GtkButton". [<--- Verify ME!]
- /* Create a button the user can click to get targets */
+The example rc file below, sets the properties of the main window, and lets
+all children of that main window inherit the style described by the "main
+button" style. The code used in the application is:
- button = gtk_button_new_with_label ("Get Targets");
- gtk_container_add (GTK_CONTAINER (window), button);
+<tscreen><verb>
+window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+gtk_widget_set_name (window, "main window");
+</verb></tscreen>
- gtk_signal_connect (GTK_OBJECT(button), "clicked",
- GTK_SIGNAL_FUNC (get_targets), NULL);
- gtk_signal_connect (GTK_OBJECT(button), "selection_received",
- GTK_SIGNAL_FUNC (selection_received), NULL);
+And then the style is defined in the rc file using:
- gtk_widget_show (button);
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+<tscreen><verb>
+widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
+Which sets all the GtkButton widgets in the "main window" to the
+"main_buttons" style as defined in the rc file.
+
+As you can see, this is a fairly powerful and flexible system. Use your
+imagination as to how best to take advantage of this.
+
<!-- ----------------------------------------------------------------- -->
-<sect1> Supplying the selection
+<sect1>GTK's rc File Format
<p>
-Supplying the selection is a bit more complicated. You must register
-handlers that will be called when your selection is requested. For
-each selection/target pair you will handle, you make a call to:
+The format of the GTK file is illustrated in the example below. This is
+the testgtkrc file from the GTK distribution, but I've added a
+few comments and things. You may wish to include this explanation
+your application to allow the user to fine tune his application.
-<tscreen><verb>
-void gtk_selection_add_handler( GtkWidget *widget,
- GdkAtom selection,
- GdkAtom target,
- GtkSelectionFunction function,
- GtkRemoveFunction remove_func,
- gpointer data );
-</verb></tscreen>
+There are several directives to change the attributes of a widget.
-<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
-this handler will manage. <tt/remove_func/, if not
-NULL, will be called when the signal handler is removed. This is
-useful, for instance, for interpreted languages which need to
-keep track of a reference count for <tt/data/.
+<itemize>
+<item>fg - Sets the foreground color of a widget.
+<item>bg - Sets the background color of a widget.
+<item>bg_pixmap - Sets the background of a widget to a tiled pixmap.
+<item>font - Sets the font to be used with the given widget.
+</itemize>
-The callback function has the signature:
+In addition to this, there are several states a widget can be in, and you
+can set different colors, pixmaps and fonts for each state. These states are:
-<tscreen><verb>
-typedef void (*GtkSelectionFunction)( GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data );
+<itemize>
+<item>NORMAL - The normal state of a widget, without the mouse over top of
+it, and not being pressed etc.
+<item>PRELIGHT - When the mouse is over top of the widget, colors defined
+using this state will be in effect.
+<item>ACTIVE - When the widget is pressed or clicked it will be active, and
+the attributes assigned by this tag will be in effect.
+<item>INSENSITIVE - When a widget is set insensitive, and cannot be
+activated, it will take these attributes.
+<item>SELECTED - When an object is selected, it takes these attributes.
+</itemize>
+
+When using the "fg" and "bg" keywords to set the colors of widgets, the
+format is:
+<tscreen><verb>
+fg[<STATE>] = { Red, Green, Blue }
</verb></tscreen>
-The GtkSelectionData is the same as above, but this time, we're
-responsible for filling in the fields <tt/type/, <tt/format/,
-<tt/data/, and <tt/length/. (The <tt/format/ field is actually
-important here - the X server uses it to figure out whether the data
-needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a
-character - or 32 - <em/i.e./ a. integer.) This is done by calling the
-function:
+Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red,
+Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being
+white. They must be in float form, or they will register as 0, so a straight
+"1" will not work, it must be "1.0". A straight "0" is fine because it
+doesn't matter if it's not recognized. Unrecognized values are set to 0.
+bg_pixmap is very similar to the above, except the colors are replaced by a
+filename.
+
+pixmap_path is a list of paths seperated by ":"'s. These paths will be
+searched for any pixmap you specify.
+
+The font directive is simply:
<tscreen><verb>
-void gtk_selection_data_set( GtkSelectionData *selection_data,
- GdkAtom type,
- gint format,
- guchar *data,
- gint length );
+font = "<font name>"
</verb></tscreen>
-This function takes care of properly making a copy of the data so that
-you don't have to worry about keeping it around. (You should not fill
-in the fields of the GtkSelectionData structure by hand.)
+Where the only hard part is figuring out the font string. Using xfontsel or
+similar utility should help.
+
+The "widget_class" sets the style of a class of widgets. These classes are
+listed in the widget overview on the class hierarchy.
+
+The "widget" directive sets a specificaly named set of widgets to a
+given style, overriding any style set for the given widget class.
+These widgets are registered inside the application using the
+gtk_widget_set_name() call. This allows you to specify the attributes of a
+widget on a per widget basis, rather than setting the attributes of an
+entire widget class. I urge you to document any of these special widgets so
+users may customize them.
+
+When the keyword <tt>parent</> is used as an attribute, the widget will take on
+the attributes of it's parent in the application.
-When prompted by the user, you claim ownership of the selection by
-calling:
+When defining a style, you may assign the attributes of a previously defined
+style to this new one.
<tscreen><verb>
-gint gtk_selection_owner_set( GtkWidget *widget,
- GdkAtom selection,
- guint32 time );
+style "main_button" = "button"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
+ bg[PRELIGHT] = { 0.75, 0, 0 }
+}
</verb></tscreen>
-If another application claims ownership of the selection, you will
-receive a "selection_clear_event".
+This example takes the "button" style, and creates a new "main_button" style
+simply by changing the font and prelight background color of the "button"
+style.
-As an example of supplying the selection, the following program adds
-selection functionality to a toggle button. When the toggle button is
-depressed, the program claims the primary selection. The only target
-supported (aside from certain targets like "TARGETS" supplied by GTK
-itself), is the "STRING" target. When this target is requested, a
-string representation of the time is returned.
+Of course, many of these attributes don't apply to all widgets. It's a
+simple matter of common sense really. Anything that could apply, should.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Example rc file
+<p>
<tscreen><verb>
-/* example-start selection setselection.c */
+# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
+#
+pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
+#
+# style <name> [= <name>]
+# {
+# <option>
+# }
+#
+# widget <widget_set> style <style_name>
+# widget_class <widget_class_set> style <style_name>
-#include <gtk/gtk.h>
-#include <time.h>
-/* Callback when the user toggles the selection */
-void
-selection_toggled (GtkWidget *widget, gint *have_selection)
+# Here is a list of all the possible states. Note that some do not apply to
+# certain widgets.
+#
+# NORMAL - The normal state of a widget, without the mouse over top of
+# it, and not being pressed etc.
+#
+# PRELIGHT - When the mouse is over top of the widget, colors defined
+# using this state will be in effect.
+#
+# ACTIVE - When the widget is pressed or clicked it will be active, and
+# the attributes assigned by this tag will be in effect.
+#
+# INSENSITIVE - When a widget is set insensitive, and cannot be
+# activated, it will take these attributes.
+#
+# SELECTED - When an object is selected, it takes these attributes.
+#
+# Given these states, we can set the attributes of the widgets in each of
+# these states using the following directives.
+#
+# fg - Sets the foreground color of a widget.
+# fg - Sets the background color of a widget.
+# bg_pixmap - Sets the background of a widget to a tiled pixmap.
+# font - Sets the font to be used with the given widget.
+#
+
+# This sets a style called "button". The name is not really important, as
+# it is assigned to the actual widgets at the bottom of the file.
+
+style "window"
{
- if (GTK_TOGGLE_BUTTON(widget)->active)
- {
- *have_selection = gtk_selection_owner_set (widget,
- GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME);
- /* if claiming the selection failed, we return the button to
- the out state */
- if (!*have_selection)
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
- }
- else
- {
- if (*have_selection)
- {
- /* Before clearing the selection by setting the owner to NULL,
- we check if we are the actual owner */
- if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
- gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME);
- *have_selection = FALSE;
- }
- }
+ #This sets the padding around the window to the pixmap specified.
+ #bg_pixmap[<STATE>] = "<pixmap filename>"
+ bg_pixmap[NORMAL] = "warning.xpm"
}
-/* Called when another application claims the selection */
-gint
-selection_clear (GtkWidget *widget, GdkEventSelection *event,
- gint *have_selection)
+style "scale"
{
- *have_selection = FALSE;
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
-
- return TRUE;
+ #Sets the foreground color (font color) to red when in the "NORMAL"
+ #state.
+
+ fg[NORMAL] = { 1.0, 0, 0 }
+
+ #Sets the background pixmap of this widget to that of it's parent.
+ bg_pixmap[NORMAL] = "<parent>"
}
-/* Supplies the current time as the selection. */
-void
-selection_handle (GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data)
+style "button"
{
- gchar *timestr;
- time_t current_time;
+ # This shows all the possible states for a button. The only one that
+ # doesn't apply is the SELECTED state.
+
+ fg[PRELIGHT] = { 0, 1.0, 1.0 }
+ bg[PRELIGHT] = { 0, 0, 1.0 }
+ bg[ACTIVE] = { 1.0, 0, 0 }
+ fg[ACTIVE] = { 0, 1.0, 0 }
+ bg[NORMAL] = { 1.0, 1.0, 0 }
+ fg[NORMAL] = { .99, 0, .99 }
+ bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
+ fg[INSENSITIVE] = { 1.0, 0, 1.0 }
+}
- current_time = time (NULL);
- timestr = asctime (localtime(&current_time));
- /* When we return a single string, it should not be null terminated.
- That will be done for us */
+# In this example, we inherit the attributes of the "button" style and then
+# override the font and background color when prelit to create a new
+# "main_button" style.
- gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
- 8, timestr, strlen(timestr));
+style "main_button" = "button"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
+ bg[PRELIGHT] = { 0.75, 0, 0 }
}
-int
-main (int argc, char *argv[])
+style "toggle_button" = "button"
{
- GtkWidget *window;
-
- GtkWidget *selection_button;
-
- static int have_selection = FALSE;
+ fg[NORMAL] = { 1.0, 0, 0 }
+ fg[ACTIVE] = { 1.0, 0, 0 }
- gtk_init (&argc, &argv);
-
- /* Create the toplevel window */
-
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+ # This sets the background pixmap of the toggle_button to that of it's
+ # parent widget (as defined in the application).
+ bg_pixmap[NORMAL] = "<parent>"
+}
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
+style "text"
+{
+ bg_pixmap[NORMAL] = "marble.xpm"
+ fg[NORMAL] = { 1.0, 1.0, 1.0 }
+}
- /* Create a toggle button to act as the selection */
+style "ruler"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
+}
- selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
- gtk_container_add (GTK_CONTAINER (window), selection_button);
- gtk_widget_show (selection_button);
+# pixmap_path "~/.pixmaps"
- gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
- GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
- gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
- GTK_SIGNAL_FUNC (selection_clear), &have_selection);
+# These set the widget types to use the styles defined above.
+# The widget types are listed in the class hierarchy, but could probably be
+# just listed in this document for the users reference.
- gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
- GDK_SELECTION_TYPE_STRING,
- selection_handle, NULL);
+widget_class "GtkWindow" style "window"
+widget_class "GtkDialog" style "window"
+widget_class "GtkFileSelection" style "window"
+widget_class "*Gtk*Scale" style "scale"
+widget_class "*GtkCheckButton*" style "toggle_button"
+widget_class "*GtkRadioButton*" style "toggle_button"
+widget_class "*GtkButton*" style "button"
+widget_class "*Ruler" style "ruler"
+widget_class "*GtkText" style "text"
- gtk_widget_show (selection_button);
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+# This sets all the buttons that are children of the "main window" to
+# the main_buton style. These must be documented to be taken advantage of.
+widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
-
<!-- ***************************************************************** -->
-<sect>glib<label id="sec_glib">
+<sect>Writing Your Own Widgets
<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
<p>
-glib provides many useful functions and definitions available for use
-when creating GDK and GTK applications. I will list them all here with
-a brief explanation. Many are duplicates of standard libc functions so
-I won't go into detail on those. This is mostly to be used as a reference,
-so you know what is available for use.
+Although the GTK distribution comes with many types of widgets that
+should cover most basic needs, there may come a time when you need to
+create your own new widget type. Since GTK uses widget inheretence
+extensively, and there is already a widget that is close to what you want,
+it is often possible to make a useful new widget type in
+just a few lines of code. But before starting work on a new widget, check
+around first to make sure that someone has not already written
+it. This will prevent duplication of effort and keep the number of
+GTK widgets out there to a minimum, which will help keep both the code
+and the interface of different applications consistent. As a flip side
+to this, once you finish your widget, announce it to the world so
+other people can benefit. The best place to do this is probably the
+<tt>gtk-list</tt>.
+
+Complete sources for the example widgets are available at the place you
+got this tutorial, or from:
+
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+
<!-- ----------------------------------------------------------------- -->
-<sect1>Definitions
+<sect1> The Anatomy Of A Widget
<p>
-Definitions for the extremes of many of the standard types are:
+In order to create a new widget, it is important to have an
+understanding of how GTK objects work. This section is just meant as a
+brief overview. See the reference documentation for the details.
+
+GTK widgets are implemented in an object oriented fashion. However,
+they are implemented in standard C. This greatly improves portability
+and stability over using current generation C++ compilers; however,
+it does mean that the widget writer has to pay attention to some of
+the implementation details. The information common to all instances of
+one class of widgets (e.g., to all Button widgets) is stored in the
+<em>class structure</em>. There is only one copy of this in
+which is stored information about the class's signals
+(which act like virtual functions in C). To support inheritance, the
+first field in the class structure must be a copy of the parent's
+class structure. The declaration of the class structure of GtkButtton
+looks like:
<tscreen><verb>
-G_MINFLOAT
-G_MAXFLOAT
-G_MINDOUBLE
-G_MAXDOUBLE
-G_MINSHORT
-G_MAXSHORT
-G_MININT
-G_MAXINT
-G_MINLONG
-G_MAXLONG
+struct _GtkButtonClass
+{
+ GtkContainerClass parent_class;
+
+ void (* pressed) (GtkButton *button);
+ void (* released) (GtkButton *button);
+ void (* clicked) (GtkButton *button);
+ void (* enter) (GtkButton *button);
+ void (* leave) (GtkButton *button);
+};
</verb></tscreen>
-Also, the following typedefs. The ones left unspecified are dynamically set
-depending on the architecture. Remember to avoid counting on the size of a
-pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4
-on Intel.
+When a button is treated as a container (for instance, when it is
+resized), its class structure can be cast to GtkContainerClass, and
+the relevant fields used to handle the signals.
+
+There is also a structure for each widget that is created on a
+per-instance basis. This structure has fields to store information that
+is different for each instance of the widget. We'll call this
+structure the <em>object structure</em>. For the Button class, it looks
+like:
<tscreen><verb>
-char gchar;
-short gshort;
-long glong;
-int gint;
-char gboolean;
+struct _GtkButton
+{
+ GtkContainer container;
-unsigned char guchar;
-unsigned short gushort;
-unsigned long gulong;
-unsigned int guint;
+ GtkWidget *child;
-float gfloat;
-double gdouble;
-long double gldouble;
+ guint in_button : 1;
+ guint button_down : 1;
+};
+</verb></tscreen>
-void* gpointer;
+Note that, similar to the class structure, the first field is the
+object structure of the parent class, so that this structure can be
+cast to the parent class's object structure as needed.
-gint8
-guint8
-gint16
-guint16
-gint32
-guint32
-</verb></tscreen>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Creating a Composite widget
<!-- ----------------------------------------------------------------- -->
-<sect1>Doubly Linked Lists
+<sect2> Introduction
<p>
-The following functions are used to create, manage, and destroy doubly
-linked lists. I assume you know what linked lists are, as it is beyond the scope
-of this document to explain them. Of course, it's not required that you
-know these for general use of GTK, but they are nice to know.
-
-<tscreen><verb>
-GList *g_list_alloc( void );
+One type of widget that you may be interested in creating is a
+widget that is merely an aggregate of other GTK widgets. This type of
+widget does nothing that couldn't be done without creating new
+widgets, but provides a convenient way of packaging user interface
+elements for reuse. The FileSelection and ColorSelection widgets in
+the standard distribution are examples of this type of widget.
-void g_list_free( GList *list );
+The example widget that we'll create in this section is the Tictactoe
+widget, a 3x3 array of toggle buttons which triggers a signal when all
+three buttons in a row, column, or on one of the diagonals are
+depressed.
-void g_list_free_1( GList *list );
+<!-- ----------------------------------------------------------------- -->
+<sect2> Choosing a parent class
+<p>
+The parent class for a composite widget is typically the container
+class that holds all of the elements of the composite widget. For
+example, the parent class of the FileSelection widget is the
+Dialog class. Since our buttons will be arranged in a table, it
+might seem natural to make our parent class the GtkTable
+class. Unfortunately, this turns out not to work. The creation of a
+widget is divided among two functions - a <tt/WIDGETNAME_new()/
+function that the user calls, and a <tt/WIDGETNAME_init()/ function
+which does the basic work of initializing the widget which is
+independent of the arguments passed to the <tt/_new()/
+function. Descendent widgets only call the <tt/_init/ function of
+their parent widget. But this division of labor doesn't work well for
+tables, which when created, need to know the number of rows and
+columns in the table. Unless we want to duplicate most of the
+functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had
+best avoid deriving it from GtkTable. For that reason, we derive it
+from GtkVBox instead, and stick our table inside the VBox.
-GList *g_list_append( GList *list,
- gpointer data );
-
-GList *g_list_prepend( GList *list,
- gpointer data );
-
-GList *g_list_insert( GList *list,
- gpointer data,
- gint position );
+<!-- ----------------------------------------------------------------- -->
+<sect2> The header file
+<p>
+Each widget class has a header file which declares the object and
+class structures for that widget, along with public functions.
+A couple of features are worth pointing out. To prevent duplicate
+definitions, we wrap the entire header file in:
-GList *g_list_remove( GList *list,
- gpointer data );
-
-GList *g_list_remove_link( GList *list,
- GList *link );
+<tscreen><verb>
+#ifndef __TICTACTOE_H__
+#define __TICTACTOE_H__
+.
+.
+.
+#endif /* __TICTACTOE_H__ */
+</verb></tscreen>
-GList *g_list_reverse( GList *list );
+And to keep C++ programs that include the header file happy, in:
-GList *g_list_nth( GList *list,
- gint n );
-
-GList *g_list_find( GList *list,
- gpointer data );
+<tscreen><verb>
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+.
+.
+.
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+</verb></tscreen>
-GList *g_list_last( GList *list );
+Along with the functions and structures, we declare three standard
+macros in our header file, <tt/TICTACTOE(obj)/,
+<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a
+pointer into a pointer to the object or class structure, and check
+if an object is a Tictactoe widget respectively.
-GList *g_list_first( GList *list );
+Here is the complete header file:
-gint g_list_length( GList *list );
+<tscreen><verb>
+/* tictactoe.h */
-void g_list_foreach( GList *list,
- GFunc func,
- gpointer user_data );
-</verb></tscreen>
+#ifndef __TICTACTOE_H__
+#define __TICTACTOE_H__
-<!-- ----------------------------------------------------------------- -->
-<sect1>Singly Linked Lists
-<p>
-Many of the above functions for singly linked lists are identical to the
-above. Here is a complete list:
-<tscreen><verb>
-GSList *g_slist_alloc( void );
+#include <gdk/gdk.h>
+#include <gtk/gtkvbox.h>
-void g_slist_free( GSList *list );
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-void g_slist_free_1( GSList *list );
+#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
+#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
+#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
-GSList *g_slist_append( GSList *list,
- gpointer data );
-
-GSList *g_slist_prepend( GSList *list,
- gpointer data );
-
-GSList *g_slist_insert( GSList *list,
- gpointer data,
- gint position );
-
-GSList *g_slist_remove( GSList *list,
- gpointer data );
-
-GSList *g_slist_remove_link( GSList *list,
- GSList *link );
-
-GSList *g_slist_reverse( GSList *list );
-GSList *g_slist_nth( GSList *list,
- gint n );
-
-GSList *g_slist_find( GSList *list,
- gpointer data );
-
-GSList *g_slist_last( GSList *list );
+typedef struct _Tictactoe Tictactoe;
+typedef struct _TictactoeClass TictactoeClass;
-gint g_slist_length( GSList *list );
+struct _Tictactoe
+{
+ GtkVBox vbox;
+
+ GtkWidget *buttons[3][3];
+};
-void g_slist_foreach( GSList *list,
- GFunc func,
- gpointer user_data );
-
-</verb></tscreen>
+struct _TictactoeClass
+{
+ GtkVBoxClass parent_class;
-<!-- ----------------------------------------------------------------- -->
-<sect1>Memory Management
-<p>
-<tscreen><verb>
-gpointer g_malloc( gulong size );
-</verb></tscreen>
+ void (* tictactoe) (Tictactoe *ttt);
+};
-This is a replacement for malloc(). You do not need to check the return
-vaule as it is done for you in this function.
+guint tictactoe_get_type (void);
+GtkWidget* tictactoe_new (void);
+void tictactoe_clear (Tictactoe *ttt);
-<tscreen><verb>
-gpointer g_malloc0( gulong size );
-</verb></tscreen>
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-Same as above, but zeroes the memory before returning a pointer to it.
+#endif /* __TICTACTOE_H__ */
-<tscreen><verb>
-gpointer g_realloc( gpointer mem,
- gulong size );
</verb></tscreen>
-Relocates "size" bytes of memory starting at "mem". Obviously, the
-memory should have been previously allocated.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The <tt/_get_type()/ function.
+<p>
+We now continue on to the implementation of our widget. A core
+function for every widget is the function
+<tt/WIDGETNAME_get_type()/. This function, when first called, tells
+GTK about the widget class, and gets an ID that uniquely identifies
+the widget class. Upon subsequent calls, it just returns the ID.
<tscreen><verb>
-void g_free( gpointer mem );
-</verb></tscreen>
+guint
+tictactoe_get_type ()
+{
+ static guint ttt_type = 0;
-Frees memory. Easy one.
+ if (!ttt_type)
+ {
+ GtkTypeInfo ttt_info =
+ {
+ "Tictactoe",
+ sizeof (Tictactoe),
+ sizeof (TictactoeClass),
+ (GtkClassInitFunc) tictactoe_class_init,
+ (GtkObjectInitFunc) tictactoe_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL
+ };
-<tscreen><verb>
-void g_mem_profile( void );
+ ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
+ }
+
+ return ttt_type;
+}
</verb></tscreen>
-Dumps a profile of used memory, but requries that you add #define
-MEM_PROFILE to the top of glib/gmem.c and re-make and make install.
+The GtkTypeInfo structure has the following definition:
<tscreen><verb>
-void g_mem_check( gpointer mem );
+struct _GtkTypeInfo
+{
+ gchar *type_name;
+ guint object_size;
+ guint class_size;
+ GtkClassInitFunc class_init_func;
+ GtkObjectInitFunc object_init_func;
+ GtkArgSetFunc arg_set_func;
+ GtkArgGetFunc arg_get_func;
+};
</verb></tscreen>
-Checks that a memory location is valid. Requires you add #define
-MEM_CHECK to the top of gmem.c and re-make and make install.
+The fields of this structure are pretty self-explanatory. We'll ignore
+the <tt/arg_set_func/ and <tt/arg_get_func/ fields here: they have an important,
+but as yet largely
+unimplemented, role in allowing widget options to be conveniently set
+from interpreted languages. Once GTK has a correctly filled in copy of
+this structure, it knows how to create objects of a particular widget
+type.
<!-- ----------------------------------------------------------------- -->
-<sect1>Timers
+<sect2> The <tt/_class_init()/ function
<p>
-Timer functions..
+The <tt/WIDGETNAME_class_init()/ function initializes the fields of
+the widget's class structure, and sets up any signals for the
+class. For our Tictactoe widget it looks like:
<tscreen><verb>
-GTimer *g_timer_new( void );
-void g_timer_destroy( GTimer *timer );
+enum {
+ TICTACTOE_SIGNAL,
+ LAST_SIGNAL
+};
-void g_timer_start( GTimer *timer );
+static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
-void g_timer_stop( GTimer *timer );
+static void
+tictactoe_class_init (TictactoeClass *class)
+{
+ GtkObjectClass *object_class;
-void g_timer_reset( GTimer *timer );
+ object_class = (GtkObjectClass*) class;
+
+ tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
+ gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
-gdouble g_timer_elapsed( GTimer *timer,
- gulong *microseconds );
-</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1>String Handling
-<p>
-A whole mess of string handling functions. They all look very interesting, and
-probably better for many purposes than the standard C string functions, but
-require documentation.
+ gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
-<tscreen><verb>
-GString *g_string_new( gchar *init );
+ class->tictactoe = NULL;
+}
+</verb></tscreen>
-void g_string_free( GString *string,
- gint free_segment );
-
-GString *g_string_assign( GString *lval,
- gchar *rval );
-
-GString *g_string_truncate( GString *string,
- gint len );
-
-GString *g_string_append( GString *string,
- gchar *val );
-
-GString *g_string_append_c( GString *string,
- gchar c );
-
-GString *g_string_prepend( GString *string,
- gchar *val );
-
-GString *g_string_prepend_c( GString *string,
- gchar c );
-
-void g_string_sprintf( GString *string,
- gchar *fmt,
- ...);
-
-void g_string_sprintfa ( GString *string,
- gchar *fmt,
- ... );
-</verb></tscreen>
+Our widget has just one signal, the <tt/tictactoe/ signal that is
+invoked when a row, column, or diagonal is completely filled in. Not
+every composite widget needs signals, so if you are reading this for
+the first time, you may want to skip to the next section now, as
+things are going to get a bit complicated.
+
+The function:
-<!-- ----------------------------------------------------------------- -->
-<sect1>Utility and Error Functions
-<p>
<tscreen><verb>
-gchar *g_strdup( const gchar *str );
+gint gtk_signal_new( const gchar *name,
+ GtkSignalRunType run_type,
+ GtkType object_type,
+ gint function_offset,
+ GtkSignalMarshaller marshaller,
+ GtkType return_val,
+ guint nparams,
+ ...);
</verb></tscreen>
-Replacement strdup function. Copies the original strings contents to
-newly allocated memory, and returns a pointer to it.
+Creates a new signal. The parameters are:
-<tscreen><verb>
-gchar *g_strerror( gint errnum );
-</verb></tscreen>
+<itemize>
+<item> <tt/name/: The name of the signal.
+<item> <tt/run_type/: Whether the default handler runs before or after
+user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/,
+although there are other possibilities.
+<item> <tt/object_type/: The ID of the object that this signal applies
+to. (It will also apply to that objects descendents)
+<item> <tt/function_offset/: The offset within the class structure of
+a pointer to the default handler.
+<item> <tt/marshaller/: A function that is used to invoke the signal
+handler. For signal handlers that have no arguments other than the
+object that emitted the signal and user data, we can use the
+pre-supplied marshaller function <tt/gtk_signal_default_marshaller/.
+<item> <tt/return_val/: The type of the return val.
+<item> <tt/nparams/: The number of parameters of the signal handler
+(other than the two default ones mentioned above)
+<item> <tt/.../: The types of the parameters.
+</itemize>
-I recommend using this for all error messages. It's much nicer, and more
-portable than perror() or others. The output is usually of the form:
+When specifying types, the <tt/GtkType/ enumeration is used:
<tscreen><verb>
-program name:function that failed:file or further description:strerror
-</verb></tscreen>
+typedef enum
+{
+ GTK_TYPE_INVALID,
+ GTK_TYPE_NONE,
+ GTK_TYPE_CHAR,
+ GTK_TYPE_BOOL,
+ GTK_TYPE_INT,
+ GTK_TYPE_UINT,
+ GTK_TYPE_LONG,
+ GTK_TYPE_ULONG,
+ GTK_TYPE_FLOAT,
+ GTK_TYPE_DOUBLE,
+ GTK_TYPE_STRING,
+ GTK_TYPE_ENUM,
+ GTK_TYPE_FLAGS,
+ GTK_TYPE_BOXED,
+ GTK_TYPE_FOREIGN,
+ GTK_TYPE_CALLBACK,
+ GTK_TYPE_ARGS,
-Here's an example of one such call used in our hello_world program:
+ GTK_TYPE_POINTER,
-<tscreen><verb>
-g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
-</verb></tscreen>
+ /* it'd be great if the next two could be removed eventually */
+ GTK_TYPE_SIGNAL,
+ GTK_TYPE_C_CALLBACK,
-<tscreen><verb>
-void g_error( gchar *format, ... );
+ GTK_TYPE_OBJECT
+
+} GtkFundamentalType;
</verb></tscreen>
-Prints an error message. The format is just like printf, but it
-prepends "** ERROR **: " to your message, and exits the program.
-Use only for fatal errors.
+<tt/gtk_signal_new()/ returns a unique integer identifier for the
+signal, that we store in the <tt/tictactoe_signals/ array, which we
+index using an enumeration. (Conventionally, the enumeration elements
+are the signal name, uppercased, but here there would be a conflict
+with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/
+instead.
+
+After creating our signals, we need to tell GTK to associate our
+signals with the Tictactoe class. We do that by calling
+<tt/gtk_object_class_add_signals()/. We then set the pointer which
+points to the default handler for the ``tictactoe'' signal to NULL,
+indicating that there is no default action.
+
+<!-- ----------------------------------------------------------------- -->
+<sect2> The <tt/_init()/ function.
+<p>
+Each widget class also needs a function to initialize the object
+structure. Usually, this function has the fairly limited role of
+setting the fields of the structure to default values. For composite
+widgets, however, this function also creates the component widgets.
<tscreen><verb>
-void g_warning( gchar *format, ... );
+static void
+tictactoe_init (Tictactoe *ttt)
+{
+ GtkWidget *table;
+ gint i,j;
+
+ table = gtk_table_new (3, 3, TRUE);
+ gtk_container_add (GTK_CONTAINER(ttt), table);
+ gtk_widget_show (table);
+
+ for (i=0;i<3; i++)
+ for (j=0;j<3; j++)
+ {
+ ttt->buttons[i][j] = gtk_toggle_button_new ();
+ gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
+ i, i+1, j, j+1);
+ gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
+ GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
+ gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
+ gtk_widget_show (ttt->buttons[i][j]);
+ }
+}
</verb></tscreen>
-Same as above, but prepends "** WARNING **: ", and does not exit the
-program.
-
-<tscreen><verb>
-void g_message( gchar *format, ... );
-</verb></tscreen>
+<!-- ----------------------------------------------------------------- -->
+<sect2> And the rest...
+<p>
+There is one more function that every widget (except for base widget
+types like GtkBin that cannot be instantiated) needs to have - the
+function that the user calls to create an object of that type. This is
+conventionally called <tt/WIDGETNAME_new()/. In some
+widgets, though not for the Tictactoe widgets, this function takes
+arguments, and does some setup based on the arguments. The other two
+functions are specific to the Tictactoe widget.
-Prints "message: " prepended to the string you pass in.
+<tt/tictactoe_clear()/ is a public function that resets all the
+buttons in the widget to the up position. Note the use of
+<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for
+button toggles from being triggered unnecessarily.
-<tscreen><verb>
-void g_print( gchar *format, ... );
-</verb></tscreen>
+<tt/tictactoe_toggle()/ is the signal handler that is invoked when the
+user clicks on a button. It checks to see if there are any winning
+combinations that involve the toggled button, and if so, emits
+the "tictactoe" signal.
-Replacement for printf().
+<tscreen><verb>
+GtkWidget*
+tictactoe_new ()
+{
+ return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
+}
-And our last function:
+void
+tictactoe_clear (Tictactoe *ttt)
+{
+ int i,j;
-<tscreen><verb>
-gchar *g_strsignal( gint signum );
-</verb></tscreen>
+ for (i=0;i<3;i++)
+ for (j=0;j<3;j++)
+ {
+ gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
+ FALSE);
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
+ }
+}
-Prints out the name of the Unix system signal given the signal number.
-Useful in generic signal handling functions.
+static void
+tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
+{
+ int i,k;
-All of the above are more or less just stolen from glib.h. If anyone cares
-to document any function, just send me an email!
+ static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
+ { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
+ { 0, 1, 2 }, { 0, 1, 2 } };
+ static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
+ { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
+ { 0, 1, 2 }, { 2, 1, 0 } };
-<!-- ***************************************************************** -->
-<sect>GTK's rc Files
-<!-- ***************************************************************** -->
-<p>
-GTK has it's own way of dealing with application defaults, by using rc
-files. These can be used to set the colors of just about any widget, and
-can also be used to tile pixmaps onto the background of some widgets.
+ int success, found;
-<!-- ----------------------------------------------------------------- -->
-<sect1>Functions For rc Files
-<p>
-When your application starts, you should include a call to:
+ for (k=0; k<8; k++)
+ {
+ success = TRUE;
+ found = FALSE;
-<tscreen><verb>
-void gtk_rc_parse( char *filename );
+ for (i=0;i<3;i++)
+ {
+ success = success &&
+ GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
+ found = found ||
+ ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
+ }
+
+ if (success && found)
+ {
+ gtk_signal_emit (GTK_OBJECT (ttt),
+ tictactoe_signals[TICTACTOE_SIGNAL]);
+ break;
+ }
+ }
+}
</verb></tscreen>
-Passing in the filename of your rc file. This will cause GTK to parse this
-file, and use the style settings for the widget types defined there.
-
-If you wish to have a special set of widgets that can take on a different
-style from others, or any other logical division of widgets, use a call to:
+And finally, an example program using our Tictactoe widget:
<tscreen><verb>
-void gtk_widget_set_name( GtkWidget *widget,
- gchar *name );
-</verb></tscreen>
-
-Passing your newly created widget as the first argument, and the name
-you wish to give it as the second. This will allow you to change the
-attributes of this widget by name through the rc file.
+#include <gtk/gtk.h>
+#include "tictactoe.h"
-If we use a call something like this:
+/* Invoked when a row, column or diagonal is completed */
+void
+win (GtkWidget *widget, gpointer data)
+{
+ g_print ("Yay!\n");
+ tictactoe_clear (TICTACTOE (widget));
+}
-<tscreen><verb>
-button = gtk_button_new_with_label ("Special Button");
-gtk_widget_set_name (button, "special button");
-</verb></tscreen>
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *ttt;
+
+ gtk_init (&argc, &argv);
-Then this button is given the name "special button" and may be addressed by
-name in the rc file as "special button.GtkButton". [<--- Verify ME!]
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-The example rc file below, sets the properties of the main window, and lets
-all children of that main window inherit the style described by the "main
-button" style. The code used in the application is:
+ /* Create a new Tictactoe widget */
+ ttt = tictactoe_new ();
+ gtk_container_add (GTK_CONTAINER (window), ttt);
+ gtk_widget_show (ttt);
-<tscreen><verb>
-window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-gtk_widget_set_name (window, "main window");
-</verb></tscreen>
+ /* And attach to its "tictactoe" signal */
+ gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
+ GTK_SIGNAL_FUNC (win), NULL);
-And then the style is defined in the rc file using:
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
-<tscreen><verb>
-widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
-Which sets all the GtkButton widgets in the "main window" to the
-"main_buttons" style as defined in the rc file.
-
-As you can see, this is a fairly powerful and flexible system. Use your
-imagination as to how best to take advantage of this.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Creating a widget from scratch.
<!-- ----------------------------------------------------------------- -->
-<sect1>GTK's rc File Format
+<sect2> Introduction
<p>
-The format of the GTK file is illustrated in the example below. This is
-the testgtkrc file from the GTK distribution, but I've added a
-few comments and things. You may wish to include this explanation
-your application to allow the user to fine tune his application.
-
-There are several directives to change the attributes of a widget.
-
-<itemize>
-<item>fg - Sets the foreground color of a widget.
-<item>bg - Sets the background color of a widget.
-<item>bg_pixmap - Sets the background of a widget to a tiled pixmap.
-<item>font - Sets the font to be used with the given widget.
-</itemize>
+In this section, we'll learn more about how widgets display themselves
+on the screen and interact with events. As an example of this, we'll
+create an analog dial widget with a pointer that the user can drag to
+set the value.
-In addition to this, there are several states a widget can be in, and you
-can set different colors, pixmaps and fonts for each state. These states are:
+<!-- ----------------------------------------------------------------- -->
+<sect2> Displaying a widget on the screen
+<p>
+There are several steps that are involved in displaying on the screen.
+After the widget is created with a call to <tt/WIDGETNAME_new()/,
+several more functions are needed:
<itemize>
-<item>NORMAL - The normal state of a widget, without the mouse over top of
-it, and not being pressed etc.
-<item>PRELIGHT - When the mouse is over top of the widget, colors defined
-using this state will be in effect.
-<item>ACTIVE - When the widget is pressed or clicked it will be active, and
-the attributes assigned by this tag will be in effect.
-<item>INSENSITIVE - When a widget is set insensitive, and cannot be
-activated, it will take these attributes.
-<item>SELECTED - When an object is selected, it takes these attributes.
+<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X
+window for the widget if it has one.
+<item> <tt/WIDGETNAME_map()/ is invoked after the user calls
+<tt/gtk_widget_show()/. It is responsible for making sure the widget
+is actually drawn on the screen (<em/mapped/). For a container class,
+it must also make calls to <tt/map()/> functions of any child widgets.
+<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/
+is called for the widget or one of its ancestors. It makes the actual
+calls to the drawing functions to draw the widget on the screen. For
+container widgets, this function must make calls to
+<tt/gtk_widget_draw()/ for its child widgets.
+<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the
+widget. It makes the necessary calls to the drawing functions to draw
+the exposed portion on the screen. For container widgets, this
+function must generate expose events for its child widgets which don't
+have their own windows. (If they have their own windows, then X will
+generate the necessary expose events)
</itemize>
-When using the "fg" and "bg" keywords to set the colors of widgets, the
-format is:
+You might notice that the last two functions are quite similar - each
+is responsible for drawing the widget on the screen. In fact many
+types of widgets don't really care about the difference between the
+two. The default <tt/draw()/ function in the widget class simply
+generates a synthetic expose event for the redrawn area. However, some
+types of widgets can save work by distinguishing between the two
+functions. For instance, if a widget has multiple X windows, then
+since expose events identify the exposed window, it can redraw only
+the affected window, which is not possible for calls to <tt/draw()/.
-<tscreen><verb>
-fg[<STATE>] = { Red, Green, Blue }
-</verb></tscreen>
+Container widgets, even if they don't care about the difference for
+themselves, can't simply use the default <tt/draw()/ function because
+their child widgets might care about the difference. However,
+it would be wasteful to duplicate the drawing code between the two
+functions. The convention is that such widgets have a function called
+<tt/WIDGETNAME_paint()/ that does the actual work of drawing the
+widget, that is then called by the <tt/draw()/ and <tt/expose()/
+functions.
-Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red,
-Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being
-white. They must be in float form, or they will register as 0, so a straight
-"1" will not work, it must be "1.0". A straight "0" is fine because it
-doesn't matter if it's not recognized. Unrecognized values are set to 0.
+In our example approach, since the dial widget is not a container
+widget, and only has a single window, we can take the simplest
+approach and use the default <tt/draw()/ function and only implement
+an <tt/expose()/ function.
-bg_pixmap is very similar to the above, except the colors are replaced by a
-filename.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The origins of the Dial Widget
+<p>
+Just as all land animals are just variants on the first amphibian that
+crawled up out of the mud, Gtk widgets tend to start off as variants
+of some other, previously written widget. Thus, although this section
+is entilted ``Creating a Widget from Scratch'', the Dial widget really
+began with the source code for the Range widget. This was picked as a
+starting point because it would be nice if our Dial had the same
+interface as the Scale widgets which are just specialized descendents
+of the Range widget. So, though the source code is presented below in
+finished form, it should not be implied that it was written, <em>deus
+ex machina</em> in this fashion. Also, if you aren't yet familiar with
+how scale widgets work from the application writer's point of view, it
+would be a good idea to look them over before continuing.
-pixmap_path is a list of paths seperated by ":"'s. These paths will be
-searched for any pixmap you specify.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The Basics
+<p>
+Quite a bit of our widget should look pretty familiar from the
+Tictactoe widget. First, we have a header file:
-The font directive is simply:
<tscreen><verb>
-font = "<font name>"
-</verb></tscreen>
-
-Where the only hard part is figuring out the font string. Using xfontsel or
-similar utility should help.
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
-The "widget_class" sets the style of a class of widgets. These classes are
-listed in the widget overview on the class hierarchy.
+#ifndef __GTK_DIAL_H__
+#define __GTK_DIAL_H__
-The "widget" directive sets a specificaly named set of widgets to a
-given style, overriding any style set for the given widget class.
-These widgets are registered inside the application using the
-gtk_widget_set_name() call. This allows you to specify the attributes of a
-widget on a per widget basis, rather than setting the attributes of an
-entire widget class. I urge you to document any of these special widgets so
-users may customize them.
+#include <gdk/gdk.h>
+#include <gtk/gtkadjustment.h>
+#include <gtk/gtkwidget.h>
-When the keyword <tt>parent</> is used as an attribute, the widget will take on
-the attributes of it's parent in the application.
-When defining a style, you may assign the attributes of a previously defined
-style to this new one.
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-<tscreen><verb>
-style "main_button" = "button"
-{
- font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
- bg[PRELIGHT] = { 0.75, 0, 0 }
-}
-</verb></tscreen>
-This example takes the "button" style, and creates a new "main_button" style
-simply by changing the font and prelight background color of the "button"
-style.
+#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
+#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
+#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
-Of course, many of these attributes don't apply to all widgets. It's a
-simple matter of common sense really. Anything that could apply, should.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Example rc file
-<p>
+typedef struct _GtkDial GtkDial;
+typedef struct _GtkDialClass GtkDialClass;
-<tscreen><verb>
-# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
-#
-pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
-#
-# style <name> [= <name>]
-# {
-# <option>
-# }
-#
-# widget <widget_set> style <style_name>
-# widget_class <widget_class_set> style <style_name>
+struct _GtkDial
+{
+ GtkWidget widget;
+ /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
+ guint policy : 2;
-# Here is a list of all the possible states. Note that some do not apply to
-# certain widgets.
-#
-# NORMAL - The normal state of a widget, without the mouse over top of
-# it, and not being pressed etc.
-#
-# PRELIGHT - When the mouse is over top of the widget, colors defined
-# using this state will be in effect.
-#
-# ACTIVE - When the widget is pressed or clicked it will be active, and
-# the attributes assigned by this tag will be in effect.
-#
-# INSENSITIVE - When a widget is set insensitive, and cannot be
-# activated, it will take these attributes.
-#
-# SELECTED - When an object is selected, it takes these attributes.
-#
-# Given these states, we can set the attributes of the widgets in each of
-# these states using the following directives.
-#
-# fg - Sets the foreground color of a widget.
-# fg - Sets the background color of a widget.
-# bg_pixmap - Sets the background of a widget to a tiled pixmap.
-# font - Sets the font to be used with the given widget.
-#
+ /* Button currently pressed or 0 if none */
+ guint8 button;
-# This sets a style called "button". The name is not really important, as
-# it is assigned to the actual widgets at the bottom of the file.
+ /* Dimensions of dial components */
+ gint radius;
+ gint pointer_width;
-style "window"
-{
- #This sets the padding around the window to the pixmap specified.
- #bg_pixmap[<STATE>] = "<pixmap filename>"
- bg_pixmap[NORMAL] = "warning.xpm"
-}
+ /* ID of update timer, or 0 if none */
+ guint32 timer;
-style "scale"
-{
- #Sets the foreground color (font color) to red when in the "NORMAL"
- #state.
-
- fg[NORMAL] = { 1.0, 0, 0 }
-
- #Sets the background pixmap of this widget to that of it's parent.
- bg_pixmap[NORMAL] = "<parent>"
-}
+ /* Current angle */
+ gfloat angle;
-style "button"
-{
- # This shows all the possible states for a button. The only one that
- # doesn't apply is the SELECTED state.
-
- fg[PRELIGHT] = { 0, 1.0, 1.0 }
- bg[PRELIGHT] = { 0, 0, 1.0 }
- bg[ACTIVE] = { 1.0, 0, 0 }
- fg[ACTIVE] = { 0, 1.0, 0 }
- bg[NORMAL] = { 1.0, 1.0, 0 }
- fg[NORMAL] = { .99, 0, .99 }
- bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
- fg[INSENSITIVE] = { 1.0, 0, 1.0 }
-}
+ /* Old values from adjustment stored so we know when something changes */
+ gfloat old_value;
+ gfloat old_lower;
+ gfloat old_upper;
-# In this example, we inherit the attributes of the "button" style and then
-# override the font and background color when prelit to create a new
-# "main_button" style.
+ /* The adjustment object that stores the data for this dial */
+ GtkAdjustment *adjustment;
+};
-style "main_button" = "button"
+struct _GtkDialClass
{
- font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
- bg[PRELIGHT] = { 0.75, 0, 0 }
-}
+ GtkWidgetClass parent_class;
+};
-style "toggle_button" = "button"
-{
- fg[NORMAL] = { 1.0, 0, 0 }
- fg[ACTIVE] = { 1.0, 0, 0 }
-
- # This sets the background pixmap of the toggle_button to that of it's
- # parent widget (as defined in the application).
- bg_pixmap[NORMAL] = "<parent>"
-}
-style "text"
-{
- bg_pixmap[NORMAL] = "marble.xpm"
- fg[NORMAL] = { 1.0, 1.0, 1.0 }
-}
+GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
+guint gtk_dial_get_type (void);
+GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
+void gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy);
-style "ruler"
-{
- font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
+void gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment);
+#ifdef __cplusplus
}
+#endif /* __cplusplus */
-# pixmap_path "~/.pixmaps"
-# These set the widget types to use the styles defined above.
-# The widget types are listed in the class hierarchy, but could probably be
-# just listed in this document for the users reference.
+#endif /* __GTK_DIAL_H__ */
+</verb></tscreen>
-widget_class "GtkWindow" style "window"
-widget_class "GtkDialog" style "window"
-widget_class "GtkFileSelection" style "window"
-widget_class "*Gtk*Scale" style "scale"
-widget_class "*GtkCheckButton*" style "toggle_button"
-widget_class "*GtkRadioButton*" style "toggle_button"
-widget_class "*GtkButton*" style "button"
-widget_class "*Ruler" style "ruler"
-widget_class "*GtkText" style "text"
+Since there is quite a bit more going on in this widget, than the last
+one, we have more fields in the data structure, but otherwise things
+are pretty similar.
-# This sets all the buttons that are children of the "main window" to
-# the main_buton style. These must be documented to be taken advantage of.
-widget "main window.*GtkButton*" style "main_button"
-</verb></tscreen>
+Next, after including header files, and declaring a few constants,
+we have some functions to provide information about the widget
+and initialize it:
-<!-- ***************************************************************** -->
-<sect>Writing Your Own Widgets
-<!-- ***************************************************************** -->
+<tscreen><verb>
+#include <math.h>
+#include <stdio.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
-<p>
-Although the GTK distribution comes with many types of widgets that
-should cover most basic needs, there may come a time when you need to
-create your own new widget type. Since GTK uses widget inheretence
-extensively, and there is already a widget that is close to what you want,
-it is often possible to make a useful new widget type in
-just a few lines of code. But before starting work on a new widget, check
-around first to make sure that someone has not already written
-it. This will prevent duplication of effort and keep the number of
-GTK widgets out there to a minimum, which will help keep both the code
-and the interface of different applications consistent. As a flip side
-to this, once you finish your widget, announce it to the world so
-other people can benefit. The best place to do this is probably the
-<tt>gtk-list</tt>.
+#include "gtkdial.h"
-Complete sources for the example widgets are available at the place you
-got this tutorial, or from:
+#define SCROLL_DELAY_LENGTH 300
+#define DIAL_DEFAULT_SIZE 100
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+/* Forward declararations */
+[ omitted to save space ]
-<!-- ----------------------------------------------------------------- -->
-<sect1> The Anatomy Of A Widget
-<p>
-In order to create a new widget, it is important to have an
-understanding of how GTK objects work. This section is just meant as a
-brief overview. See the reference documentation for the details.
+/* Local data */
-GTK widgets are implemented in an object oriented fashion. However,
-they are implemented in standard C. This greatly improves portability
-and stability over using current generation C++ compilers; however,
-it does mean that the widget writer has to pay attention to some of
-the implementation details. The information common to all instances of
-one class of widgets (e.g., to all Button widgets) is stored in the
-<em>class structure</em>. There is only one copy of this in
-which is stored information about the class's signals
-(which act like virtual functions in C). To support inheritance, the
-first field in the class structure must be a copy of the parent's
-class structure. The declaration of the class structure of GtkButtton
-looks like:
+static GtkWidgetClass *parent_class = NULL;
-<tscreen><verb>
-struct _GtkButtonClass
+guint
+gtk_dial_get_type ()
{
- GtkContainerClass parent_class;
+ static guint dial_type = 0;
- void (* pressed) (GtkButton *button);
- void (* released) (GtkButton *button);
- void (* clicked) (GtkButton *button);
- void (* enter) (GtkButton *button);
- void (* leave) (GtkButton *button);
-};
-</verb></tscreen>
+ if (!dial_type)
+ {
+ GtkTypeInfo dial_info =
+ {
+ "GtkDial",
+ sizeof (GtkDial),
+ sizeof (GtkDialClass),
+ (GtkClassInitFunc) gtk_dial_class_init,
+ (GtkObjectInitFunc) gtk_dial_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL,
+ };
-When a button is treated as a container (for instance, when it is
-resized), its class structure can be cast to GtkContainerClass, and
-the relevant fields used to handle the signals.
+ dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
+ }
-There is also a structure for each widget that is created on a
-per-instance basis. This structure has fields to store information that
-is different for each instance of the widget. We'll call this
-structure the <em>object structure</em>. For the Button class, it looks
-like:
+ return dial_type;
+}
-<tscreen><verb>
-struct _GtkButton
+static void
+gtk_dial_class_init (GtkDialClass *class)
{
- GtkContainer container;
-
- GtkWidget *child;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
- guint in_button : 1;
- guint button_down : 1;
-};
-</verb></tscreen>
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
-Note that, similar to the class structure, the first field is the
-object structure of the parent class, so that this structure can be
-cast to the parent class's object structure as needed.
+ parent_class = gtk_type_class (gtk_widget_get_type ());
-<!-- ----------------------------------------------------------------- -->
-<sect1> Creating a Composite widget
+ object_class->destroy = gtk_dial_destroy;
-<!-- ----------------------------------------------------------------- -->
-<sect2> Introduction
-<p>
-One type of widget that you may be interested in creating is a
-widget that is merely an aggregate of other GTK widgets. This type of
-widget does nothing that couldn't be done without creating new
-widgets, but provides a convenient way of packaging user interface
-elements for reuse. The FileSelection and ColorSelection widgets in
-the standard distribution are examples of this type of widget.
+ widget_class->realize = gtk_dial_realize;
+ widget_class->expose_event = gtk_dial_expose;
+ widget_class->size_request = gtk_dial_size_request;
+ widget_class->size_allocate = gtk_dial_size_allocate;
+ widget_class->button_press_event = gtk_dial_button_press;
+ widget_class->button_release_event = gtk_dial_button_release;
+ widget_class->motion_notify_event = gtk_dial_motion_notify;
+}
-The example widget that we'll create in this section is the Tictactoe
-widget, a 3x3 array of toggle buttons which triggers a signal when all
-three buttons in a row, column, or on one of the diagonals are
-depressed.
+static void
+gtk_dial_init (GtkDial *dial)
+{
+ dial->button = 0;
+ dial->policy = GTK_UPDATE_CONTINUOUS;
+ dial->timer = 0;
+ dial->radius = 0;
+ dial->pointer_width = 0;
+ dial->angle = 0.0;
+ dial->old_value = 0.0;
+ dial->old_lower = 0.0;
+ dial->old_upper = 0.0;
+ dial->adjustment = NULL;
+}
-<!-- ----------------------------------------------------------------- -->
-<sect2> Choosing a parent class
-<p>
-The parent class for a composite widget is typically the container
-class that holds all of the elements of the composite widget. For
-example, the parent class of the FileSelection widget is the
-Dialog class. Since our buttons will be arranged in a table, it
-might seem natural to make our parent class the GtkTable
-class. Unfortunately, this turns out not to work. The creation of a
-widget is divided among two functions - a <tt/WIDGETNAME_new()/
-function that the user calls, and a <tt/WIDGETNAME_init()/ function
-which does the basic work of initializing the widget which is
-independent of the arguments passed to the <tt/_new()/
-function. Descendent widgets only call the <tt/_init/ function of
-their parent widget. But this division of labor doesn't work well for
-tables, which when created, need to know the number of rows and
-columns in the table. Unless we want to duplicate most of the
-functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had
-best avoid deriving it from GtkTable. For that reason, we derive it
-from GtkVBox instead, and stick our table inside the VBox.
+GtkWidget*
+gtk_dial_new (GtkAdjustment *adjustment)
+{
+ GtkDial *dial;
-<!-- ----------------------------------------------------------------- -->
-<sect2> The header file
-<p>
-Each widget class has a header file which declares the object and
-class structures for that widget, along with public functions.
-A couple of features are worth pointing out. To prevent duplicate
-definitions, we wrap the entire header file in:
+ dial = gtk_type_new (gtk_dial_get_type ());
-<tscreen><verb>
-#ifndef __TICTACTOE_H__
-#define __TICTACTOE_H__
-.
-.
-.
-#endif /* __TICTACTOE_H__ */
-</verb></tscreen>
+ if (!adjustment)
+ adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-And to keep C++ programs that include the header file happy, in:
+ gtk_dial_set_adjustment (dial, adjustment);
-<tscreen><verb>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-.
-.
-.
-#ifdef __cplusplus
+ return GTK_WIDGET (dial);
}
-#endif /* __cplusplus */
-</verb></tscreen>
-Along with the functions and structures, we declare three standard
-macros in our header file, <tt/TICTACTOE(obj)/,
-<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a
-pointer into a pointer to the object or class structure, and check
-if an object is a Tictactoe widget respectively.
+static void
+gtk_dial_destroy (GtkObject *object)
+{
+ GtkDial *dial;
-Here is the complete header file:
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_DIAL (object));
-<tscreen><verb>
-/* tictactoe.h */
+ dial = GTK_DIAL (object);
-#ifndef __TICTACTOE_H__
-#define __TICTACTOE_H__
+ if (dial->adjustment)
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
-#include <gdk/gdk.h>
-#include <gtk/gtkvbox.h>
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+</verb></tscreen>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+Note that this <tt/init()/ function does less than for the Tictactoe
+widget, since this is not a composite widget, and the <tt/new()/
+function does more, since it now has an argument. Also, note that when
+we store a pointer to the Adjustment object, we increment its
+reference count, (and correspondingly decrement when we no longer use
+it) so that GTK can keep track of when it can be safely destroyed.
-#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
-#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
-#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
+<p>
+Also, there are a few function to manipulate the widget's options:
+<tscreen><verb>
+GtkAdjustment*
+gtk_dial_get_adjustment (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-typedef struct _Tictactoe Tictactoe;
-typedef struct _TictactoeClass TictactoeClass;
+ return dial->adjustment;
+}
-struct _Tictactoe
+void
+gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy)
{
- GtkVBox vbox;
-
- GtkWidget *buttons[3][3];
-};
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
+
+ dial->policy = policy;
+}
-struct _TictactoeClass
+void
+gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment)
{
- GtkVBoxClass parent_class;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
- void (* tictactoe) (Tictactoe *ttt);
-};
+ if (dial->adjustment)
+ {
+ gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
+ }
-guint tictactoe_get_type (void);
-GtkWidget* tictactoe_new (void);
-void tictactoe_clear (Tictactoe *ttt);
+ dial->adjustment = adjustment;
+ gtk_object_ref (GTK_OBJECT (dial->adjustment));
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+ gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
+ (GtkSignalFunc) gtk_dial_adjustment_changed,
+ (gpointer) dial);
+ gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
+ (GtkSignalFunc) gtk_dial_adjustment_value_changed,
+ (gpointer) dial);
-#endif /* __TICTACTOE_H__ */
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ gtk_dial_update (dial);
+}
</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_get_type()/ function.
+<sect2> <tt/gtk_dial_realize()/
+
<p>
-We now continue on to the implementation of our widget. A core
-function for every widget is the function
-<tt/WIDGETNAME_get_type()/. This function, when first called, tells
-GTK about the widget class, and gets an ID that uniquely identifies
-the widget class. Upon subsequent calls, it just returns the ID.
+Now we come to some new types of functions. First, we have a function
+that does the work of creating the X window. Notice that a mask is
+passed to the function <tt/gdk_window_new()/ which specifies which fields of
+the GdkWindowAttr structure actually have data in them (the remaining
+fields wll be given default values). Also worth noting is the way the
+event mask of the widget is created. We call
+<tt/gtk_widget_get_events()/ to retrieve the event mask that the user
+has specified for this widget (with <tt/gtk_widget_set_events()/, and
+add the events that we are interested in ourselves.
+
+<p>
+After creating the window, we set its style and background, and put a
+pointer to the widget in the user data field of the GdkWindow. This
+last step allows GTK to dispatch events for this window to the correct
+widget.
<tscreen><verb>
-guint
-tictactoe_get_type ()
+static void
+gtk_dial_realize (GtkWidget *widget)
{
- static guint ttt_type = 0;
+ GtkDial *dial;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
- if (!ttt_type)
- {
- GtkTypeInfo ttt_info =
- {
- "Tictactoe",
- sizeof (Tictactoe),
- sizeof (TictactoeClass),
- (GtkClassInitFunc) tictactoe_class_init,
- (GtkObjectInitFunc) tictactoe_init,
- (GtkArgSetFunc) NULL,
- (GtkArgGetFunc) NULL
- };
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
- ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
- }
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+ dial = GTK_DIAL (widget);
- return ttt_type;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+ widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ gdk_window_set_user_data (widget->window, widget);
+
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
</verb></tscreen>
-The GtkTypeInfo structure has the following definition:
+<sect2> Size negotiation
+
+<p>
+Before the first time that the window containing a widget is
+displayed, and whenever the layout of the window changes, GTK asks
+each child widget for its desired size. This request is handled by the
+function, <tt/gtk_dial_size_request()/. Since our widget isn't a
+container widget, and has no real constraints on its size, we just
+return a reasonable default value.
<tscreen><verb>
-struct _GtkTypeInfo
+static void
+gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
{
- gchar *type_name;
- guint object_size;
- guint class_size;
- GtkClassInitFunc class_init_func;
- GtkObjectInitFunc object_init_func;
- GtkArgSetFunc arg_set_func;
- GtkArgGetFunc arg_get_func;
-};
+ requisition->width = DIAL_DEFAULT_SIZE;
+ requisition->height = DIAL_DEFAULT_SIZE;
+}
</verb></tscreen>
-The fields of this structure are pretty self-explanatory. We'll ignore
-the <tt/arg_set_func/ and <tt/arg_get_func/ fields here: they have an important,
-but as yet largely
-unimplemented, role in allowing widget options to be conveniently set
-from interpreted languages. Once GTK has a correctly filled in copy of
-this structure, it knows how to create objects of a particular widget
-type.
-
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_class_init()/ function
<p>
-The <tt/WIDGETNAME_class_init()/ function initializes the fields of
-the widget's class structure, and sets up any signals for the
-class. For our Tictactoe widget it looks like:
+After all the widgets have requested an ideal size, the layout of the
+window is computed and each child widget is notified of its actual
+size. Usually, this will at least as large as the requested size, but
+if for instance, the user has resized the window, it may occasionally
+be smaller than the requested size. The size notification is handled
+by the function <tt/gtk_dial_size_allocate()/. Notice that as well as
+computing the sizes of some component pieces for future use, this
+routine also does the grunt work of moving the widgets X window into
+the new position and size.
<tscreen><verb>
-
-enum {
- TICTACTOE_SIGNAL,
- LAST_SIGNAL
-};
-
-static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
-
static void
-tictactoe_class_init (TictactoeClass *class)
+gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
{
- GtkObjectClass *object_class;
+ GtkDial *dial;
- object_class = (GtkObjectClass*) class;
-
- tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
- GTK_RUN_FIRST,
- object_class->type,
- GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
- gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
+ g_return_if_fail (allocation != NULL);
+ widget->allocation = *allocation;
+ if (GTK_WIDGET_REALIZED (widget))
+ {
+ dial = GTK_DIAL (widget);
- gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
- class->tictactoe = NULL;
+ dial->radius = MAX(allocation->width,allocation->height) * 0.45;
+ dial->pointer_width = dial->radius / 5;
+ }
}
-</verb></tscreen>
+</verb></tscreen>.
-Our widget has just one signal, the <tt/tictactoe/ signal that is
-invoked when a row, column, or diagonal is completely filled in. Not
-every composite widget needs signals, so if you are reading this for
-the first time, you may want to skip to the next section now, as
-things are going to get a bit complicated.
+<!-- ----------------------------------------------------------------- -->
+<sect2> <tt/gtk_dial_expose()/
-The function:
+<p>
+As mentioned above, all the drawing of this widget is done in the
+handler for expose events. There's not much to remark on here except
+the use of the function <tt/gtk_draw_polygon/ to draw the pointer with
+three dimensional shading according to the colors stored in the
+widget's style.
<tscreen><verb>
-gint gtk_signal_new( const gchar *name,
- GtkSignalRunType run_type,
- GtkType object_type,
- gint function_offset,
- GtkSignalMarshaller marshaller,
- GtkType return_val,
- guint nparams,
- ...);
-</verb></tscreen>
+static gint
+gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GtkDial *dial;
+ GdkPoint points[3];
+ gdouble s,c;
+ gdouble theta;
+ gint xc, yc;
+ gint tick_length;
+ gint i;
-Creates a new signal. The parameters are:
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
-<itemize>
-<item> <tt/name/: The name of the signal.
-<item> <tt/run_type/: Whether the default handler runs before or after
-user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/,
-although there are other possibilities.
-<item> <tt/object_type/: The ID of the object that this signal applies
-to. (It will also apply to that objects descendents)
-<item> <tt/function_offset/: The offset within the class structure of
-a pointer to the default handler.
-<item> <tt/marshaller/: A function that is used to invoke the signal
-handler. For signal handlers that have no arguments other than the
-object that emitted the signal and user data, we can use the
-pre-supplied marshaller function <tt/gtk_signal_default_marshaller/.
-<item> <tt/return_val/: The type of the return val.
-<item> <tt/nparams/: The number of parameters of the signal handler
-(other than the two default ones mentioned above)
-<item> <tt/.../: The types of the parameters.
-</itemize>
+ if (event->count > 0)
+ return FALSE;
+
+ dial = GTK_DIAL (widget);
-When specifying types, the <tt/GtkType/ enumeration is used:
+ gdk_window_clear_area (widget->window,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
-<tscreen><verb>
-typedef enum
-{
- GTK_TYPE_INVALID,
- GTK_TYPE_NONE,
- GTK_TYPE_CHAR,
- GTK_TYPE_BOOL,
- GTK_TYPE_INT,
- GTK_TYPE_UINT,
- GTK_TYPE_LONG,
- GTK_TYPE_ULONG,
- GTK_TYPE_FLOAT,
- GTK_TYPE_DOUBLE,
- GTK_TYPE_STRING,
- GTK_TYPE_ENUM,
- GTK_TYPE_FLAGS,
- GTK_TYPE_BOXED,
- GTK_TYPE_FOREIGN,
- GTK_TYPE_CALLBACK,
- GTK_TYPE_ARGS,
+ xc = widget->allocation.width/2;
+ yc = widget->allocation.height/2;
- GTK_TYPE_POINTER,
+ /* Draw ticks */
- /* it'd be great if the next two could be removed eventually */
- GTK_TYPE_SIGNAL,
- GTK_TYPE_C_CALLBACK,
+ for (i=0; i<25; i++)
+ {
+ theta = (i*M_PI/18. - M_PI/6.);
+ s = sin(theta);
+ c = cos(theta);
- GTK_TYPE_OBJECT
+ tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
+
+ gdk_draw_line (widget->window,
+ widget->style->fg_gc[widget->state],
+ xc + c*(dial->radius - tick_length),
+ yc - s*(dial->radius - tick_length),
+ xc + c*dial->radius,
+ yc - s*dial->radius);
+ }
-} GtkFundamentalType;
-</verb></tscreen>
+ /* Draw pointer */
-<tt/gtk_signal_new()/ returns a unique integer identifier for the
-signal, that we store in the <tt/tictactoe_signals/ array, which we
-index using an enumeration. (Conventionally, the enumeration elements
-are the signal name, uppercased, but here there would be a conflict
-with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/
-instead.
+ s = sin(dial->angle);
+ c = cos(dial->angle);
-After creating our signals, we need to tell GTK to associate our
-signals with the Tictactoe class. We do that by calling
-<tt/gtk_object_class_add_signals()/. We then set the pointer which
-points to the default handler for the ``tictactoe'' signal to NULL,
-indicating that there is no default action.
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_init()/ function.
-<p>
-Each widget class also needs a function to initialize the object
-structure. Usually, this function has the fairly limited role of
-setting the fields of the structure to default values. For composite
-widgets, however, this function also creates the component widgets.
+ points[0].x = xc + s*dial->pointer_width/2;
+ points[0].y = yc + c*dial->pointer_width/2;
+ points[1].x = xc + c*dial->radius;
+ points[1].y = yc - s*dial->radius;
+ points[2].x = xc - s*dial->pointer_width/2;
+ points[2].y = yc - c*dial->pointer_width/2;
-<tscreen><verb>
-static void
-tictactoe_init (Tictactoe *ttt)
-{
- GtkWidget *table;
- gint i,j;
+ gtk_draw_polygon (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ points, 3,
+ TRUE);
- table = gtk_table_new (3, 3, TRUE);
- gtk_container_add (GTK_CONTAINER(ttt), table);
- gtk_widget_show (table);
-
- for (i=0;i<3; i++)
- for (j=0;j<3; j++)
- {
- ttt->buttons[i][j] = gtk_toggle_button_new ();
- gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
- i, i+1, j, j+1);
- gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
- GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
- gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
- gtk_widget_show (ttt->buttons[i][j]);
- }
+ return FALSE;
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect2> And the rest...
+<sect2> Event handling
+
<p>
-There is one more function that every widget (except for base widget
-types like GtkBin that cannot be instantiated) needs to have - the
-function that the user calls to create an object of that type. This is
-conventionally called <tt/WIDGETNAME_new()/. In some
-widgets, though not for the Tictactoe widgets, this function takes
-arguments, and does some setup based on the arguments. The other two
-functions are specific to the Tictactoe widget.
-<tt/tictactoe_clear()/ is a public function that resets all the
-buttons in the widget to the up position. Note the use of
-<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for
-button toggles from being triggered unnecessarily.
+The rest of the widget's code handles various types of events, and
+isn't too different from what would be found in many GTK
+applications. Two types of events can occur - either the user can
+click on the widget with the mouse and drag to move the pointer, or
+the value of the Adjustment object can change due to some external
+circumstance.
-<tt/tictactoe_toggle()/ is the signal handler that is invoked when the
-user clicks on a button. It checks to see if there are any winning
-combinations that involve the toggled button, and if so, emits
-the "tictactoe" signal.
+<p>
+When the user clicks on the widget, we check to see if the click was
+appropriately near the pointer, and if so, store then button that the
+user clicked with in the <tt/button/ field of the widget
+structure, and grab all mouse events with a call to
+<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the
+value of the control to be recomputed (by the function
+<tt/gtk_dial_update_mouse/). Depending on the policy that has been
+set, "value_changed" events are either generated instantly
+(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with
+<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the
+button is released (<tt/GTK_UPDATE_DISCONTINUOUS/).
-<tscreen><verb>
-GtkWidget*
-tictactoe_new ()
+<tscreen><verb>
+static gint
+gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event)
{
- return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
+ GtkDial *dial;
+ gint dx, dy;
+ double s, c;
+ double d_parallel;
+ double d_perpendicular;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ /* Determine if button press was within pointer region - we
+ do this by computing the parallel and perpendicular distance of
+ the point where the mouse was pressed from the line passing through
+ the pointer */
+
+ dx = event->x - widget->allocation.width / 2;
+ dy = widget->allocation.height / 2 - event->y;
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+ d_parallel = s*dy + c*dx;
+ d_perpendicular = fabs(s*dx - c*dy);
+
+ if (!dial->button &&
+ (d_perpendicular < dial->pointer_width/2) &&
+ (d_parallel > - dial->pointer_width))
+ {
+ gtk_grab_add (widget);
+
+ dial->button = event->button;
+
+ gtk_dial_update_mouse (dial, event->x, event->y);
+ }
+
+ return FALSE;
}
-void
-tictactoe_clear (Tictactoe *ttt)
+static gint
+gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event)
{
- int i,j;
+ GtkDial *dial;
- for (i=0;i<3;i++)
- for (j=0;j<3;j++)
- {
- gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
- FALSE);
- gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
- }
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ if (dial->button == event->button)
+ {
+ gtk_grab_remove (widget);
+
+ dial->button = 0;
+
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_timeout_remove (dial->timer);
+
+ if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
+ (dial->old_value != dial->adjustment->value))
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+
+ return FALSE;
}
-static void
-tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
+static gint
+gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
{
- int i,k;
+ GtkDial *dial;
+ GdkModifierType mods;
+ gint x, y, mask;
- static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
- { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
- { 0, 1, 2 }, { 0, 1, 2 } };
- static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
- { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
- { 0, 1, 2 }, { 2, 1, 0 } };
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
- int success, found;
+ dial = GTK_DIAL (widget);
- for (k=0; k<8; k++)
+ if (dial->button != 0)
{
- success = TRUE;
- found = FALSE;
+ x = event->x;
+ y = event->y;
- for (i=0;i<3;i++)
- {
- success = success &&
- GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
- found = found ||
- ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
- }
-
- if (success && found)
+ if (event->is_hint || (event->window != widget->window))
+ gdk_window_get_pointer (widget->window, &x, &y, &mods);
+
+ switch (dial->button)
{
- gtk_signal_emit (GTK_OBJECT (ttt),
- tictactoe_signals[TICTACTOE_SIGNAL]);
+ case 1:
+ mask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ mask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ mask = GDK_BUTTON3_MASK;
+ break;
+ default:
+ mask = 0;
break;
}
- }
-}
-</verb></tscreen>
-And finally, an example program using our Tictactoe widget:
-
-<tscreen><verb>
-#include <gtk/gtk.h>
-#include "tictactoe.h"
+ if (mods & mask)
+ gtk_dial_update_mouse (dial, x,y);
+ }
-/* Invoked when a row, column or diagonal is completed */
-void
-win (GtkWidget *widget, gpointer data)
-{
- g_print ("Yay!\n");
- tictactoe_clear (TICTACTOE (widget));
+ return FALSE;
}
-int
-main (int argc, char *argv[])
+static gint
+gtk_dial_timer (GtkDial *dial)
{
- GtkWidget *window;
- GtkWidget *ttt;
-
- gtk_init (&argc, &argv);
+ g_return_val_if_fail (dial != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
-
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- /* Create a new Tictactoe widget */
- ttt = tictactoe_new ();
- gtk_container_add (GTK_CONTAINER (window), ttt);
- gtk_widget_show (ttt);
+ return FALSE;
+}
- /* And attach to its "tictactoe" signal */
- gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
- GTK_SIGNAL_FUNC (win), NULL);
+static void
+gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+{
+ gint xc, yc;
+ gfloat old_value;
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-</verb></tscreen>
+ xc = GTK_WIDGET(dial)->allocation.width / 2;
+ yc = GTK_WIDGET(dial)->allocation.height / 2;
-<!-- ----------------------------------------------------------------- -->
-<sect1> Creating a widget from scratch.
+ old_value = dial->adjustment->value;
+ dial->angle = atan2(yc-y, x-xc);
-<!-- ----------------------------------------------------------------- -->
-<sect2> Introduction
-<p>
-In this section, we'll learn more about how widgets display themselves
-on the screen and interact with events. As an example of this, we'll
-create an analog dial widget with a pointer that the user can drag to
-set the value.
+ if (dial->angle < -M_PI/2.)
+ dial->angle += 2*M_PI;
-<!-- ----------------------------------------------------------------- -->
-<sect2> Displaying a widget on the screen
-<p>
-There are several steps that are involved in displaying on the screen.
-After the widget is created with a call to <tt/WIDGETNAME_new()/,
-several more functions are needed:
+ if (dial->angle < -M_PI/6)
+ dial->angle = -M_PI/6;
-<itemize>
-<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X
-window for the widget if it has one.
-<item> <tt/WIDGETNAME_map()/ is invoked after the user calls
-<tt/gtk_widget_show()/. It is responsible for making sure the widget
-is actually drawn on the screen (<em/mapped/). For a container class,
-it must also make calls to <tt/map()/> functions of any child widgets.
-<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/
-is called for the widget or one of its ancestors. It makes the actual
-calls to the drawing functions to draw the widget on the screen. For
-container widgets, this function must make calls to
-<tt/gtk_widget_draw()/ for its child widgets.
-<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the
-widget. It makes the necessary calls to the drawing functions to draw
-the exposed portion on the screen. For container widgets, this
-function must generate expose events for its child widgets which don't
-have their own windows. (If they have their own windows, then X will
-generate the necessary expose events)
-</itemize>
+ if (dial->angle > 7.*M_PI/6.)
+ dial->angle = 7.*M_PI/6.;
-You might notice that the last two functions are quite similar - each
-is responsible for drawing the widget on the screen. In fact many
-types of widgets don't really care about the difference between the
-two. The default <tt/draw()/ function in the widget class simply
-generates a synthetic expose event for the redrawn area. However, some
-types of widgets can save work by distinguishing between the two
-functions. For instance, if a widget has multiple X windows, then
-since expose events identify the exposed window, it can redraw only
-the affected window, which is not possible for calls to <tt/draw()/.
+ dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
+ (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
-Container widgets, even if they don't care about the difference for
-themselves, can't simply use the default <tt/draw()/ function because
-their child widgets might care about the difference. However,
-it would be wasteful to duplicate the drawing code between the two
-functions. The convention is that such widgets have a function called
-<tt/WIDGETNAME_paint()/ that does the actual work of drawing the
-widget, that is then called by the <tt/draw()/ and <tt/expose()/
-functions.
+ if (dial->adjustment->value != old_value)
+ {
+ if (dial->policy == GTK_UPDATE_CONTINUOUS)
+ {
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ else
+ {
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
-In our example approach, since the dial widget is not a container
-widget, and only has a single window, we can take the simplest
-approach and use the default <tt/draw()/ function and only implement
-an <tt/expose()/ function.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ {
+ if (dial->timer)
+ gtk_timeout_remove (dial->timer);
-<!-- ----------------------------------------------------------------- -->
-<sect2> The origins of the Dial Widget
-<p>
-Just as all land animals are just variants on the first amphibian that
-crawled up out of the mud, Gtk widgets tend to start off as variants
-of some other, previously written widget. Thus, although this section
-is entilted ``Creating a Widget from Scratch'', the Dial widget really
-began with the source code for the Range widget. This was picked as a
-starting point because it would be nice if our Dial had the same
-interface as the Scale widgets which are just specialized descendents
-of the Range widget. So, though the source code is presented below in
-finished form, it should not be implied that it was written, <em>deus
-ex machina</em> in this fashion. Also, if you aren't yet familiar with
-how scale widgets work from the application writer's point of view, it
-would be a good idea to look them over before continuing.
+ dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
+ (GtkFunction) gtk_dial_timer,
+ (gpointer) dial);
+ }
+ }
+ }
+}
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> The Basics
<p>
-Quite a bit of our widget should look pretty familiar from the
-Tictactoe widget. First, we have a header file:
+Changes to the Adjustment by external means are communicated to our
+widget by the ``changed'' and ``value_changed'' signals. The handlers
+for these functions call <tt/gtk_dial_update()/ to validate the
+arguments, compute the new pointer angle, and redraw the widget (by
+calling <tt/gtk_widget_draw()/).
<tscreen><verb>
-/* GTK - The GIMP Toolkit
- * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
+static void
+gtk_dial_update (GtkDial *dial)
+{
+ gfloat new_value;
+
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-#ifndef __GTK_DIAL_H__
-#define __GTK_DIAL_H__
+ new_value = dial->adjustment->value;
+
+ if (new_value < dial->adjustment->lower)
+ new_value = dial->adjustment->lower;
-#include <gdk/gdk.h>
-#include <gtk/gtkadjustment.h>
-#include <gtk/gtkwidget.h>
+ if (new_value > dial->adjustment->upper)
+ new_value = dial->adjustment->upper;
+
+ if (new_value != dial->adjustment->value)
+ {
+ dial->adjustment->value = new_value;
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
+ (dial->adjustment->upper - dial->adjustment->lower);
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
+}
+static void
+gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
-#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
-#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
+ dial = GTK_DIAL (data);
-typedef struct _GtkDial GtkDial;
-typedef struct _GtkDialClass GtkDialClass;
+ if ((dial->old_value != adjustment->value) ||
+ (dial->old_lower != adjustment->lower) ||
+ (dial->old_upper != adjustment->upper))
+ {
+ gtk_dial_update (dial);
+
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ }
+}
-struct _GtkDial
+static void
+gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data)
{
- GtkWidget widget;
+ GtkDial *dial;
- /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
- guint policy : 2;
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
- /* Button currently pressed or 0 if none */
- guint8 button;
+ dial = GTK_DIAL (data);
- /* Dimensions of dial components */
- gint radius;
- gint pointer_width;
+ if (dial->old_value != adjustment->value)
+ {
+ gtk_dial_update (dial);
- /* ID of update timer, or 0 if none */
- guint32 timer;
+ dial->old_value = adjustment->value;
+ }
+}
+</verb></tscreen>
- /* Current angle */
- gfloat angle;
+<!-- ----------------------------------------------------------------- -->
+<sect2> Possible Enhancements
+<p>
- /* Old values from adjustment stored so we know when something changes */
- gfloat old_value;
- gfloat old_lower;
- gfloat old_upper;
+The Dial widget as we've described it so far runs about 670 lines of
+code. Although that might sound like a fair bit, we've really
+accomplished quite a bit with that much code, especially since much of
+that length is headers and boilerplate. However, there are quite a few
+more enhancements that could be made to this widget:
- /* The adjustment object that stores the data for this dial */
- GtkAdjustment *adjustment;
-};
+<itemize>
+<item> If you try this widget out, you'll find that there is some
+flashing as the pointer is dragged around. This is because the entire
+widget is erased every time the pointer is moved before being
+redrawn. Often, the best way to handle this problem is to draw to an
+offscreen pixmap, then copy the final results onto the screen in one
+step. (The ProgressBar widget draws itself in this fashion.)
-struct _GtkDialClass
-{
- GtkWidgetClass parent_class;
-};
+<item> The user should be able to use the up and down arrow keys to
+increase and decrease the value.
+<item> It would be nice if the widget had buttons to increase and
+decrease the value in small or large steps. Although it would be
+possible to use embedded Button widgets for this, we would also like
+the buttons to auto-repeat when held down, as the arrows on a
+scrollbar do. Most of the code to implement this type of behavior can
+be found in the GtkRange widget.
-GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
-guint gtk_dial_get_type (void);
-GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
-void gtk_dial_set_update_policy (GtkDial *dial,
- GtkUpdateType policy);
+<item> The Dial widget could be made into a container widget with a
+single child widget positioned at the bottom between the buttons
+mentioned above. The user could then add their choice of a label or
+entry widget to display the current value of the dial.
-void gtk_dial_set_adjustment (GtkDial *dial,
- GtkAdjustment *adjustment);
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+</itemize>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Learning More
-#endif /* __GTK_DIAL_H__ */
-</verb></tscreen>
+<p>
+Only a small part of the many details involved in creating widgets
+could be described above. If you want to write your own widgets, the
+best source of examples is the GTK source itself. Ask yourself some
+questions about the widget you want to write: is it a Container
+widget? does it have its own window? is it a modification of an
+existing widget? Then find a similar widget, and start making changes.
+Good luck!
-Since there is quite a bit more going on in this widget, than the last
-one, we have more fields in the data structure, but otherwise things
-are pretty similar.
+<!-- ***************************************************************** -->
+<sect>Scribble, A Simple Example Drawing Program
+<!-- ***************************************************************** -->
-Next, after including header files, and declaring a few constants,
-we have some functions to provide information about the widget
-and initialize it:
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
-<tscreen><verb>
-#include <math.h>
-#include <stdio.h>
-#include <gtk/gtkmain.h>
-#include <gtk/gtksignal.h>
+<p>
+In this section, we will build a simple drawing program. In the
+process, we will examine how to handle mouse events, how to draw in a
+window, and how to do drawing better by using a backing pixmap. After
+creating the simple drawing program, we will extend it by adding
+support for XInput devices, such as drawing tablets. GTK provides
+support routines which makes getting extended information, such as
+pressure and tilt, from such devices quite easy.
-#include "gtkdial.h"
+<!-- ----------------------------------------------------------------- -->
+<sect1> Event Handling
-#define SCROLL_DELAY_LENGTH 300
-#define DIAL_DEFAULT_SIZE 100
+<p>
+The GTK signals we have already discussed are for high-level actions,
+such as a menu item being selected. However, sometimes it is useful to
+learn about lower-level occurrences, such as the mouse being moved, or
+a key being pressed. There are also GTK signals corresponding to these
+low-level <em>events</em>. The handlers for these signals have an
+extra parameter which is a pointer to a structure containing
+information about the event. For instance, motion events handlers are
+passed a pointer to a GdkEventMotion structure which looks (in part)
+like:
-/* Forward declararations */
+<tscreen><verb>
+struct _GdkEventMotion
+{
+ GdkEventType type;
+ GdkWindow *window;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ ...
+ guint state;
+ ...
+};
+</verb></tscreen>
-[ omitted to save space ]
+<tt/type/ will be set to the event type, in this case
+<tt/GDK_MOTION_NOTIFY/, window is the window in which the event
+occured. <tt/x/ and <tt/y/ give the coordinates of the event,
+and <tt/state/ specifies the modifier state when the event
+occurred (that is, it specifies which modifier keys and mouse buttons
+were pressed.) It is the bitwise OR of some of the following:
-/* Local data */
+<tscreen><verb>
+GDK_SHIFT_MASK
+GDK_LOCK_MASK
+GDK_CONTROL_MASK
+GDK_MOD1_MASK
+GDK_MOD2_MASK
+GDK_MOD3_MASK
+GDK_MOD4_MASK
+GDK_MOD5_MASK
+GDK_BUTTON1_MASK
+GDK_BUTTON2_MASK
+GDK_BUTTON3_MASK
+GDK_BUTTON4_MASK
+GDK_BUTTON5_MASK
+</verb></tscreen>
-static GtkWidgetClass *parent_class = NULL;
+<p>
+As for other signals, to determine what happens when an event occurs
+we call <tt>gtk_signal_connect()</tt>. But we also need let GTK
+know which events we want to be notified about. To do this, we call
+the function:
-guint
-gtk_dial_get_type ()
-{
- static guint dial_type = 0;
+<tscreen><verb>
+void gtk_widget_set_events (GtkWidget *widget,
+ gint events);
+</verb></tscreen>
- if (!dial_type)
- {
- GtkTypeInfo dial_info =
- {
- "GtkDial",
- sizeof (GtkDial),
- sizeof (GtkDialClass),
- (GtkClassInitFunc) gtk_dial_class_init,
- (GtkObjectInitFunc) gtk_dial_init,
- (GtkArgSetFunc) NULL,
- (GtkArgGetFunc) NULL,
- };
+The second field specifies the events we are interested in. It
+is the bitwise OR of constants that specify different types
+of events. For future reference the event types are:
- dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
- }
+<tscreen><verb>
+GDK_EXPOSURE_MASK
+GDK_POINTER_MOTION_MASK
+GDK_POINTER_MOTION_HINT_MASK
+GDK_BUTTON_MOTION_MASK
+GDK_BUTTON1_MOTION_MASK
+GDK_BUTTON2_MOTION_MASK
+GDK_BUTTON3_MOTION_MASK
+GDK_BUTTON_PRESS_MASK
+GDK_BUTTON_RELEASE_MASK
+GDK_KEY_PRESS_MASK
+GDK_KEY_RELEASE_MASK
+GDK_ENTER_NOTIFY_MASK
+GDK_LEAVE_NOTIFY_MASK
+GDK_FOCUS_CHANGE_MASK
+GDK_STRUCTURE_MASK
+GDK_PROPERTY_CHANGE_MASK
+GDK_PROXIMITY_IN_MASK
+GDK_PROXIMITY_OUT_MASK
+</verb></tscreen>
- return dial_type;
-}
+There are a few subtle points that have to be observed when calling
+<tt/gtk_widget_set_events()/. First, it must be called before the X window
+for a GTK widget is created. In practical terms, this means you
+should call it immediately after creating the widget. Second, the
+widget must have an associated X window. For efficiency, many widget
+types do not have their own window, but draw in their parent's window.
+These widgets are:
-static void
-gtk_dial_class_init (GtkDialClass *class)
-{
- GtkObjectClass *object_class;
- GtkWidgetClass *widget_class;
+<tscreen><verb>
+GtkAlignment
+GtkArrow
+GtkBin
+GtkBox
+GtkImage
+GtkItem
+GtkLabel
+GtkPixmap
+GtkScrolledWindow
+GtkSeparator
+GtkTable
+GtkAspectFrame
+GtkFrame
+GtkVBox
+GtkHBox
+GtkVSeparator
+GtkHSeparator
+</verb></tscreen>
- object_class = (GtkObjectClass*) class;
- widget_class = (GtkWidgetClass*) class;
+To capture events for these widgets, you need to use an EventBox
+widget. See the section on
+<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for
+details.
- parent_class = gtk_type_class (gtk_widget_get_type ());
+<p>
+For our drawing program, we want to know when the mouse button is
+pressed and when the mouse is moved, so we specify
+<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also
+want to know when we need to redraw our window, so we specify
+<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a
+Configure event when our window size changes, we don't have to specify
+the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is
+automatically specified for all windows.
- object_class->destroy = gtk_dial_destroy;
+<p>
+It turns out, however, that there is a problem with just specifying
+<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new
+motion event to the event queue every time the user moves the mouse.
+Imagine that it takes us 0.1 seconds to handle a motion event, but the
+X server queues a new motion event every 0.05 seconds. We will soon
+get way behind the users drawing. If the user draws for 5 seconds,
+it will take us another 5 seconds to catch up after they release
+the mouse button! What we would like is to only get one motion
+event for each event we process. The way to do this is to
+specify <tt/GDK_POINTER_MOTION_HINT_MASK/.
- widget_class->realize = gtk_dial_realize;
- widget_class->expose_event = gtk_dial_expose;
- widget_class->size_request = gtk_dial_size_request;
- widget_class->size_allocate = gtk_dial_size_allocate;
- widget_class->button_press_event = gtk_dial_button_press;
- widget_class->button_release_event = gtk_dial_button_release;
- widget_class->motion_notify_event = gtk_dial_motion_notify;
-}
+<p>
+When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends
+us a motion event the first time the pointer moves after entering
+our window, or after a button press or release event. Subsequent
+motion events will be suppressed until we explicitely ask for
+the position of the pointer using the function:
-static void
-gtk_dial_init (GtkDial *dial)
-{
- dial->button = 0;
- dial->policy = GTK_UPDATE_CONTINUOUS;
- dial->timer = 0;
- dial->radius = 0;
- dial->pointer_width = 0;
- dial->angle = 0.0;
- dial->old_value = 0.0;
- dial->old_lower = 0.0;
- dial->old_upper = 0.0;
- dial->adjustment = NULL;
-}
+<tscreen><verb>
+GdkWindow* gdk_window_get_pointer (GdkWindow *window,
+ gint *x,
+ gint *y,
+ GdkModifierType *mask);
+</verb></tscreen>
-GtkWidget*
-gtk_dial_new (GtkAdjustment *adjustment)
-{
- GtkDial *dial;
+(There is another function, <tt>gtk_widget_get_pointer()</tt> which
+has a simpler interface, but turns out not to be very useful, since
+it only retrieves the position of the mouse, not whether the buttons
+are pressed.)
- dial = gtk_type_new (gtk_dial_get_type ());
+<p>
+The code to set the events for our window then looks like:
- if (!adjustment)
- adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+<tscreen><verb>
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
+ (GtkSignalFunc) expose_event, NULL);
+ gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
+ (GtkSignalFunc) configure_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
+ (GtkSignalFunc) motion_notify_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
+ (GtkSignalFunc) button_press_event, NULL);
- gtk_dial_set_adjustment (dial, adjustment);
+ gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK);
+</verb></tscreen>
- return GTK_WIDGET (dial);
-}
+We'll save the "expose_event" and "configure_event" handlers for
+later. The "motion_notify_event" and "button_press_event" handlers
+pretty simple:
-static void
-gtk_dial_destroy (GtkObject *object)
+<tscreen><verb>
+static gint
+button_press_event (GtkWidget *widget, GdkEventButton *event)
{
- GtkDial *dial;
-
- g_return_if_fail (object != NULL);
- g_return_if_fail (GTK_IS_DIAL (object));
+ if (event->button == 1 && pixmap != NULL)
+ draw_brush (widget, event->x, event->y);
- dial = GTK_DIAL (object);
+ return TRUE;
+}
- if (dial->adjustment)
- gtk_object_unref (GTK_OBJECT (dial->adjustment));
+static gint
+motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
+{
+ int x, y;
+ GdkModifierType state;
- if (GTK_OBJECT_CLASS (parent_class)->destroy)
- (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+ if (event->is_hint)
+ gdk_window_get_pointer (event->window, &x, &y, &state);
+ else
+ {
+ x = event->x;
+ y = event->y;
+ state = event->state;
+ }
+
+ if (state & GDK_BUTTON1_MASK && pixmap != NULL)
+ draw_brush (widget, x, y);
+
+ return TRUE;
}
</verb></tscreen>
-Note that this <tt/init()/ function does less than for the Tictactoe
-widget, since this is not a composite widget, and the <tt/new()/
-function does more, since it now has an argument. Also, note that when
-we store a pointer to the Adjustment object, we increment its
-reference count, (and correspondingly decrement when we no longer use
-it) so that GTK can keep track of when it can be safely destroyed.
+<!-- ----------------------------------------------------------------- -->
+<sect1> The DrawingArea Widget, And Drawing
<p>
-Also, there are a few function to manipulate the widget's options:
+We know turn to the process of drawing on the screen. The
+widget we use for this is the DrawingArea widget. A drawing area
+widget is essentially an X window and nothing more. It is a blank
+canvas in which we can draw whatever we like. A drawing area
+is created using the call:
<tscreen><verb>
-GtkAdjustment*
-gtk_dial_get_adjustment (GtkDial *dial)
-{
- g_return_val_if_fail (dial != NULL, NULL);
- g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-
- return dial->adjustment;
-}
-
-void
-gtk_dial_set_update_policy (GtkDial *dial,
- GtkUpdateType policy)
-{
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
+GtkWidget* gtk_drawing_area_new (void);
+</verb></tscreen>
- dial->policy = policy;
-}
+A default size for the widget can be specified by calling:
-void
-gtk_dial_set_adjustment (GtkDial *dial,
- GtkAdjustment *adjustment)
-{
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
+<tscreen><verb>
+void gtk_drawing_area_size (GtkDrawingArea *darea,
+ gint width,
+ gint height);
+</verb></tscreen>
- if (dial->adjustment)
- {
- gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
- gtk_object_unref (GTK_OBJECT (dial->adjustment));
- }
+This default size can be overriden, as is true for all widgets,
+by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can
+be overridden if the user manually resizes the the window containing
+the drawing area.
- dial->adjustment = adjustment;
- gtk_object_ref (GTK_OBJECT (dial->adjustment));
+<p>
+It should be noted that when we create a DrawingArea widget, we are,
+<em>completely</em> responsible for drawing the contents. If our
+window is obscured then uncovered, we get an exposure event and must
+redraw what was previously hidden.
- gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
- (GtkSignalFunc) gtk_dial_adjustment_changed,
- (gpointer) dial);
- gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
- (GtkSignalFunc) gtk_dial_adjustment_value_changed,
- (gpointer) dial);
+<p>
+Having to remember everything that was drawn on the screen so we
+can properly redraw it can, to say the least, be a nuisance. In
+addition, it can be visually distracting if portions of the
+window are cleared, then redrawn step by step. The solution to
+this problem is to use an offscreen <em>backing pixmap</em>.
+Instead of drawing directly to the screen, we draw to an image
+stored in server memory but not displayed, then when the image
+changes or new portions of the image are displayed, we copy the
+relevant portions onto the screen.
- dial->old_value = adjustment->value;
- dial->old_lower = adjustment->lower;
- dial->old_upper = adjustment->upper;
+<p>
+To create an offscreen pixmap, we call the function:
- gtk_dial_update (dial);
-}
+<tscreen><verb>
+GdkPixmap* gdk_pixmap_new (GdkWindow *window,
+ gint width,
+ gint height,
+ gint depth);
</verb></tscreen>
-<sect2> <tt/gtk_dial_realize()/
+The <tt>window</tt> parameter specifies a GDK window that this pixmap
+takes some of its properties from. <tt>width</tt> and <tt>height</tt>
+specify the size of the pixmap. <tt>depth</tt> specifies the <em>color
+depth</em>, that is the number of bits per pixel, for the new window.
+If the depth is specified as <tt>-1</tt>, it will match the depth
+of <tt>window</tt>.
<p>
-Now we come to some new types of functions. First, we have a function
-that does the work of creating the X window. Notice that a mask is
-passed to the function <tt/gdk_window_new()/ which specifies which fields of
-the GdkWindowAttr structure actually have data in them (the remaining
-fields wll be given default values). Also worth noting is the way the
-event mask of the widget is created. We call
-<tt/gtk_widget_get_events()/ to retrieve the event mask that the user
-has specified for this widget (with <tt/gtk_widget_set_events()/, and
-add the events that we are interested in ourselves.
+We create the pixmap in our "configure_event" handler. This event
+is generated whenever the window changes size, including when it
+is originally created.
+
+<tscreen><verb>
+/* Backing pixmap for drawing area */
+static GdkPixmap *pixmap = NULL;
+
+/* Create a new backing pixmap of the appropriate size */
+static gint
+configure_event (GtkWidget *widget, GdkEventConfigure *event)
+{
+ if (pixmap)
+ gdk_pixmap_unref(pixmap);
+
+ pixmap = gdk_pixmap_new(widget->window,
+ widget->allocation.width,
+ widget->allocation.height,
+ -1);
+ gdk_draw_rectangle (pixmap,
+ widget->style->white_gc,
+ TRUE,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+
+ return TRUE;
+}
+</verb></tscreen>
+
+The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap
+initially to white. We'll say more about that in a moment.
<p>
-After creating the window, we set its style and background, and put a
-pointer to the widget in the user data field of the GdkWindow. This
-last step allows GTK to dispatch events for this window to the correct
-widget.
+Our exposure event handler then simply copies the relevant portion
+of the pixmap onto the screen (we determine the area we need
+to redraw by using the event->area field of the exposure event):
<tscreen><verb>
-static void
-gtk_dial_realize (GtkWidget *widget)
+/* Redraw the screen from the backing pixmap */
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *event)
{
- GtkDial *dial;
- GdkWindowAttr attributes;
- gint attributes_mask;
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
- g_return_if_fail (widget != NULL);
- g_return_if_fail (GTK_IS_DIAL (widget));
+ return FALSE;
+}
+</verb></tscreen>
- GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
- dial = GTK_DIAL (widget);
+We've now seen how to keep the screen up to date with our pixmap, but
+how do we actually draw interesting stuff on our pixmap? There are a
+large number of calls in GTK's GDK library for drawing on
+<em>drawables</em>. A drawable is simply something that can be drawn
+upon. It can be a window, a pixmap, or a bitmap (a black and white
+image). We've already seen two such calls above,
+<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The
+complete list is:
- attributes.x = widget->allocation.x;
- attributes.y = widget->allocation.y;
- attributes.width = widget->allocation.width;
- attributes.height = widget->allocation.height;
- attributes.wclass = GDK_INPUT_OUTPUT;
- attributes.window_type = GDK_WINDOW_CHILD;
- attributes.event_mask = gtk_widget_get_events (widget) |
- GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
- GDK_POINTER_MOTION_HINT_MASK;
- attributes.visual = gtk_widget_get_visual (widget);
- attributes.colormap = gtk_widget_get_colormap (widget);
+<tscreen><verb>
+gdk_draw_line ()
+gdk_draw_rectangle ()
+gdk_draw_arc ()
+gdk_draw_polygon ()
+gdk_draw_string ()
+gdk_draw_text ()
+gdk_draw_pixmap ()
+gdk_draw_bitmap ()
+gdk_draw_image ()
+gdk_draw_points ()
+gdk_draw_segments ()
+</verb></tscreen>
- attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
- widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
+See the reference documentation or the header file
+<tt><gdk/gdk.h></tt> for further details on these functions.
+These functions all share the same first two arguments. The first
+argument is the drawable to draw upon, the second argument is a
+<em>graphics context</em> (GC).
- widget->style = gtk_style_attach (widget->style, widget->window);
+<p>
+A graphics context encapsulates information about things such as
+foreground and background color and line width. GDK has a full set of
+functions for creating and modifying graphics contexts, but to keep
+things simple we'll just use predefined graphics contexts. Each widget
+has an associated style. (Which can be modified in a gtkrc file, see
+the section GTK's rc file.) This, among other things, stores a number
+of graphics contexts. Some examples of accessing these graphics
+contexts are:
- gdk_window_set_user_data (widget->window, widget);
+<tscreen><verb>
+widget->style->white_gc
+widget->style->black_gc
+widget->style->fg_gc[GTK_STATE_NORMAL]
+widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
+</verb></tscreen>
- gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
-}
+The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and
+<tt>light_gc</tt> are indexed by a parameter of type
+<tt>GtkStateType</tt> which can take on the values:
+
+<tscreen><verb>
+GTK_STATE_NORMAL,
+GTK_STATE_ACTIVE,
+GTK_STATE_PRELIGHT,
+GTK_STATE_SELECTED,
+GTK_STATE_INSENSITIVE
</verb></tscreen>
-<sect2> Size negotiation
+For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground
+color is white and the default background color, dark blue.
<p>
-Before the first time that the window containing a widget is
-displayed, and whenever the layout of the window changes, GTK asks
-each child widget for its desired size. This request is handled by the
-function, <tt/gtk_dial_size_request()/. Since our widget isn't a
-container widget, and has no real constraints on its size, we just
-return a reasonable default value.
+Our function <tt>draw_brush()</tt>, which does the actual drawing
+on the screen, is then:
<tscreen><verb>
-static void
-gtk_dial_size_request (GtkWidget *widget,
- GtkRequisition *requisition)
+/* Draw a rectangle on the screen */
+static void
+draw_brush (GtkWidget *widget, gdouble x, gdouble y)
{
- requisition->width = DIAL_DEFAULT_SIZE;
- requisition->height = DIAL_DEFAULT_SIZE;
+ GdkRectangle update_rect;
+
+ update_rect.x = x - 5;
+ update_rect.y = y - 5;
+ update_rect.width = 10;
+ update_rect.height = 10;
+ gdk_draw_rectangle (pixmap,
+ widget->style->black_gc,
+ TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
}
</verb></tscreen>
-<p>
-After all the widgets have requested an ideal size, the layout of the
-window is computed and each child widget is notified of its actual
-size. Usually, this will at least as large as the requested size, but
-if for instance, the user has resized the window, it may occasionally
-be smaller than the requested size. The size notification is handled
-by the function <tt/gtk_dial_size_allocate()/. Notice that as well as
-computing the sizes of some component pieces for future use, this
-routine also does the grunt work of moving the widgets X window into
-the new position and size.
+After we draw the rectangle representing the brush onto the pixmap,
+we call the function:
<tscreen><verb>
-static void
-gtk_dial_size_allocate (GtkWidget *widget,
- GtkAllocation *allocation)
-{
- GtkDial *dial;
+void gtk_widget_draw (GtkWidget *widget,
+ GdkRectangle *area);
+</verb></tscreen>
- g_return_if_fail (widget != NULL);
- g_return_if_fail (GTK_IS_DIAL (widget));
- g_return_if_fail (allocation != NULL);
+which notifies X that the area given by the <tt>area</tt> parameter
+needs to be updated. X will eventually generate an expose event
+(possibly combining the areas passed in several calls to
+<tt>gtk_widget_draw()</tt>) which will cause our expose event handler
+to copy the relevant portions to the screen.
- widget->allocation = *allocation;
- if (GTK_WIDGET_REALIZED (widget))
- {
- dial = GTK_DIAL (widget);
+<p>
+We have now covered the entire drawing program except for a few
+mundane details like creating the main window. The complete
+source code is available from the location from which you got
+this tutorial, or from:
- gdk_window_move_resize (widget->window,
- allocation->x, allocation->y,
- allocation->width, allocation->height);
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
- dial->radius = MAX(allocation->width,allocation->height) * 0.45;
- dial->pointer_width = dial->radius / 5;
- }
-}
-</verb></tscreen>.
<!-- ----------------------------------------------------------------- -->
-<sect2> <tt/gtk_dial_expose()/
+<sect1> Adding XInput support
<p>
-As mentioned above, all the drawing of this widget is done in the
-handler for expose events. There's not much to remark on here except
-the use of the function <tt/gtk_draw_polygon/ to draw the pointer with
-three dimensional shading according to the colors stored in the
-widget's style.
-
-<tscreen><verb>
-static gint
-gtk_dial_expose (GtkWidget *widget,
- GdkEventExpose *event)
-{
- GtkDial *dial;
- GdkPoint points[3];
- gdouble s,c;
- gdouble theta;
- gint xc, yc;
- gint tick_length;
- gint i;
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
-
- if (event->count > 0)
- return FALSE;
-
- dial = GTK_DIAL (widget);
+It is now possible to buy quite inexpensive input devices such
+as drawing tablets, which allow drawing with a much greater
+ease of artistic expression than does a mouse. The simplest way
+to use such devices is simply as a replacement for the mouse,
+but that misses out many of the advantages of these devices,
+such as:
- gdk_window_clear_area (widget->window,
- 0, 0,
- widget->allocation.width,
- widget->allocation.height);
+<itemize>
+<item> Pressure sensitivity
+<item> Tilt reporting
+<item> Sub-pixel positioning
+<item> Multiple inputs (for example, a stylus with a point and eraser)
+</itemize>
- xc = widget->allocation.width/2;
- yc = widget->allocation.height/2;
+For information about the XInput extension, see the <htmlurl
+url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
+name="XInput-HOWTO">.
- /* Draw ticks */
+<p>
+If we examine the full definition of, for example, the GdkEventMotion
+structure, we see that it has fields to support extended device
+information.
- for (i=0; i<25; i++)
- {
- theta = (i*M_PI/18. - M_PI/6.);
- s = sin(theta);
- c = cos(theta);
+<tscreen><verb>
+struct _GdkEventMotion
+{
+ GdkEventType type;
+ GdkWindow *window;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ gdouble pressure;
+ gdouble xtilt;
+ gdouble ytilt;
+ guint state;
+ gint16 is_hint;
+ GdkInputSource source;
+ guint32 deviceid;
+};
+</verb></tscreen>
- tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
-
- gdk_draw_line (widget->window,
- widget->style->fg_gc[widget->state],
- xc + c*(dial->radius - tick_length),
- yc - s*(dial->radius - tick_length),
- xc + c*dial->radius,
- yc - s*dial->radius);
- }
+<tt/pressure/ gives the pressure as a floating point number between
+0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between
+-1 and 1, corresponding to the degree of tilt in each direction.
+<tt/source/ and <tt/deviceid/ specify the device for which the
+event occurred in two different ways. <tt/source/ gives some simple
+information about the type of device. It can take the enumeration
+values.
- /* Draw pointer */
+<tscreen><verb>
+GDK_SOURCE_MOUSE
+GDK_SOURCE_PEN
+GDK_SOURCE_ERASER
+GDK_SOURCE_CURSOR
+</verb></tscreen>
- s = sin(dial->angle);
- c = cos(dial->angle);
+<tt/deviceid/ specifies a unique numeric ID for the device. This can
+be used to find out further information about the device using the
+<tt/gdk_input_list_devices()/ call (see below). The special value
+<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually
+the mouse.)
+<sect2> Enabling extended device information
- points[0].x = xc + s*dial->pointer_width/2;
- points[0].y = yc + c*dial->pointer_width/2;
- points[1].x = xc + c*dial->radius;
- points[1].y = yc - s*dial->radius;
- points[2].x = xc - s*dial->pointer_width/2;
- points[2].y = yc - c*dial->pointer_width/2;
+<p>
+To let GTK know about our interest in the extended device information,
+we merely have to add a single line to our program:
- gtk_draw_polygon (widget->style,
- widget->window,
- GTK_STATE_NORMAL,
- GTK_SHADOW_OUT,
- points, 3,
- TRUE);
-
- return FALSE;
-}
+<tscreen><verb>
+gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> Event handling
-
-<p>
-
-The rest of the widget's code handles various types of events, and
-isn't too different from what would be found in many GTK
-applications. Two types of events can occur - either the user can
-click on the widget with the mouse and drag to move the pointer, or
-the value of the Adjustment object can change due to some external
-circumstance.
+By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that
+we are interested in extension events, but only if we don't have
+to draw our own cursor. See the section <ref
+id="sec_Further_Sophistications" name="Further Sophistications"> below
+for more information about drawing the cursor. We could also
+give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing
+to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert
+back to the default condition.
<p>
-When the user clicks on the widget, we check to see if the click was
-appropriately near the pointer, and if so, store then button that the
-user clicked with in the <tt/button/ field of the widget
-structure, and grab all mouse events with a call to
-<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the
-value of the control to be recomputed (by the function
-<tt/gtk_dial_update_mouse/). Depending on the policy that has been
-set, "value_changed" events are either generated instantly
-(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with
-<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the
-button is released (<tt/GTK_UPDATE_DISCONTINUOUS/).
+This is not completely the end of the story however. By default,
+no extension devices are enabled. We need a mechanism to allow
+users to enable and configure their extension devices. GTK provides
+the InputDialog widget to automate this process. The following
+procedure manages an InputDialog widget. It creates the dialog if
+it isn't present, and raises it to the top otherwise.
<tscreen><verb>
-static gint
-gtk_dial_button_press (GtkWidget *widget,
- GdkEventButton *event)
+void
+input_dialog_destroy (GtkWidget *w, gpointer data)
{
- GtkDial *dial;
- gint dx, dy;
- double s, c;
- double d_parallel;
- double d_perpendicular;
-
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
+ *((GtkWidget **)data) = NULL;
+}
- dial = GTK_DIAL (widget);
+void
+create_input_dialog ()
+{
+ static GtkWidget *inputd = NULL;
- /* Determine if button press was within pointer region - we
- do this by computing the parallel and perpendicular distance of
- the point where the mouse was pressed from the line passing through
- the pointer */
-
- dx = event->x - widget->allocation.width / 2;
- dy = widget->allocation.height / 2 - event->y;
-
- s = sin(dial->angle);
- c = cos(dial->angle);
-
- d_parallel = s*dy + c*dx;
- d_perpendicular = fabs(s*dx - c*dy);
-
- if (!dial->button &&
- (d_perpendicular < dial->pointer_width/2) &&
- (d_parallel > - dial->pointer_width))
+ if (!inputd)
{
- gtk_grab_add (widget);
+ inputd = gtk_input_dialog_new();
- dial->button = event->button;
+ gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
+ (GtkSignalFunc)input_dialog_destroy, &inputd);
+ gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
+ "clicked",
+ (GtkSignalFunc)gtk_widget_hide,
+ GTK_OBJECT(inputd));
+ gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
- gtk_dial_update_mouse (dial, event->x, event->y);
+ gtk_widget_show (inputd);
+ }
+ else
+ {
+ if (!GTK_WIDGET_MAPPED(inputd))
+ gtk_widget_show(inputd);
+ else
+ gdk_window_raise(inputd->window);
}
-
- return FALSE;
}
+</verb></tscreen>
-static gint
-gtk_dial_button_release (GtkWidget *widget,
- GdkEventButton *event)
-{
- GtkDial *dial;
+(You might want to take note of the way we handle this dialog. By
+connecting to the "destroy" signal, we make sure that we don't keep a
+pointer to dialog around after it is destroyed - that could lead to a
+segfault.)
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
+<p>
+The InputDialog has two buttons "Close" and "Save", which by default
+have no actions assigned to them. In the above function we make
+"Close" hide the dialog, hide the "Save" button, since we don't
+implement saving of XInput options in this program.
- dial = GTK_DIAL (widget);
+<sect2> Using extended device information
- if (dial->button == event->button)
- {
- gtk_grab_remove (widget);
+<p>
+Once we've enabled the device, we can just use the extended
+device information in the extra fields of the event structures.
+In fact, it is always safe to use this information since these
+fields will have reasonable default values even when extended
+events are not enabled.
- dial->button = 0;
+<p>
+Once change we do have to make is to call
+<tt/gdk_input_window_get_pointer()/ instead of
+<tt/gdk_window_get_pointer/. This is necessary because
+<tt/gdk_window_get_pointer/ doesn't return the extended device
+information.
- if (dial->policy == GTK_UPDATE_DELAYED)
- gtk_timeout_remove (dial->timer);
-
- if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
- (dial->old_value != dial->adjustment->value))
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
+<tscreen><verb>
+void gdk_input_window_get_pointer (GdkWindow *window,
+ guint32 deviceid,
+ gdouble *x,
+ gdouble *y,
+ gdouble *pressure,
+ gdouble *xtilt,
+ gdouble *ytilt,
+ GdkModifierType *mask);
+</verb></tscreen>
- return FALSE;
-}
+When calling this function, we need to specify the device ID as
+well as the window. Usually, we'll get the device ID from the
+<tt/deviceid/ field of an event structure. Again, this function
+will return reasonable values when extension events are not
+enabled. (In this case, <tt/event->deviceid/ will have the value
+<tt/GDK_CORE_POINTER/).
+So the basic structure of our button-press and motion event handlers,
+doesn't change much - we just need to add code to deal with the
+extended information.
+
+<tscreen><verb>
static gint
-gtk_dial_motion_notify (GtkWidget *widget,
- GdkEventMotion *event)
+button_press_event (GtkWidget *widget, GdkEventButton *event)
{
- GtkDial *dial;
- GdkModifierType mods;
- gint x, y, mask;
-
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
-
- dial = GTK_DIAL (widget);
-
- if (dial->button != 0)
- {
- x = event->x;
- y = event->y;
-
- if (event->is_hint || (event->window != widget->window))
- gdk_window_get_pointer (widget->window, &x, &y, &mods);
-
- switch (dial->button)
- {
- case 1:
- mask = GDK_BUTTON1_MASK;
- break;
- case 2:
- mask = GDK_BUTTON2_MASK;
- break;
- case 3:
- mask = GDK_BUTTON3_MASK;
- break;
- default:
- mask = 0;
- break;
- }
-
- if (mods & mask)
- gtk_dial_update_mouse (dial, x,y);
- }
+ print_button_press (event->deviceid);
+
+ if (event->button == 1 && pixmap != NULL)
+ draw_brush (widget, event->source, event->x, event->y, event->pressure);
- return FALSE;
+ return TRUE;
}
static gint
-gtk_dial_timer (GtkDial *dial)
+motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
- g_return_val_if_fail (dial != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
-
- if (dial->policy == GTK_UPDATE_DELAYED)
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ gdouble x, y;
+ gdouble pressure;
+ GdkModifierType state;
- return FALSE;
+ if (event->is_hint)
+ gdk_input_window_get_pointer (event->window, event->deviceid,
+ &x, &y, &pressure, NULL, NULL, &state);
+ else
+ {
+ x = event->x;
+ y = event->y;
+ pressure = event->pressure;
+ state = event->state;
+ }
+
+ if (state & GDK_BUTTON1_MASK && pixmap != NULL)
+ draw_brush (widget, event->source, x, y, pressure);
+
+ return TRUE;
}
+</verb></tscreen>
+
+We also need to do something with the new information. Our new
+<tt/draw_brush()/ function draws with a different color for
+each <tt/event->source/ and changes the brush size depending
+on the pressure.
+<tscreen><verb>
+/* Draw a rectangle on the screen, size depending on pressure,
+ and color on the type of device */
static void
-gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+draw_brush (GtkWidget *widget, GdkInputSource source,
+ gdouble x, gdouble y, gdouble pressure)
{
- gint xc, yc;
- gfloat old_value;
-
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
-
- xc = GTK_WIDGET(dial)->allocation.width / 2;
- yc = GTK_WIDGET(dial)->allocation.height / 2;
-
- old_value = dial->adjustment->value;
- dial->angle = atan2(yc-y, x-xc);
-
- if (dial->angle < -M_PI/2.)
- dial->angle += 2*M_PI;
-
- if (dial->angle < -M_PI/6)
- dial->angle = -M_PI/6;
-
- if (dial->angle > 7.*M_PI/6.)
- dial->angle = 7.*M_PI/6.;
-
- dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
- (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
+ GdkGC *gc;
+ GdkRectangle update_rect;
- if (dial->adjustment->value != old_value)
+ switch (source)
{
- if (dial->policy == GTK_UPDATE_CONTINUOUS)
- {
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
- else
- {
- gtk_widget_draw (GTK_WIDGET(dial), NULL);
-
- if (dial->policy == GTK_UPDATE_DELAYED)
- {
- if (dial->timer)
- gtk_timeout_remove (dial->timer);
-
- dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
- (GtkFunction) gtk_dial_timer,
- (gpointer) dial);
- }
- }
+ case GDK_SOURCE_MOUSE:
+ gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
+ break;
+ case GDK_SOURCE_PEN:
+ gc = widget->style->black_gc;
+ break;
+ case GDK_SOURCE_ERASER:
+ gc = widget->style->white_gc;
+ break;
+ default:
+ gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
}
+
+ update_rect.x = x - 10 * pressure;
+ update_rect.y = y - 10 * pressure;
+ update_rect.width = 20 * pressure;
+ update_rect.height = 20 * pressure;
+ gdk_draw_rectangle (pixmap, gc, TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
}
</verb></tscreen>
+<sect2> Finding out more about a device
+
<p>
-Changes to the Adjustment by external means are communicated to our
-widget by the ``changed'' and ``value_changed'' signals. The handlers
-for these functions call <tt/gtk_dial_update()/ to validate the
-arguments, compute the new pointer angle, and redraw the widget (by
-calling <tt/gtk_widget_draw()/).
+As an example of how to find out more about a device, our program
+will print the name of the device that generates each button
+press. To find out the name of a device, we call the function:
<tscreen><verb>
-static void
-gtk_dial_update (GtkDial *dial)
-{
- gfloat new_value;
-
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
-
- new_value = dial->adjustment->value;
-
- if (new_value < dial->adjustment->lower)
- new_value = dial->adjustment->lower;
+GList *gdk_input_list_devices (void);
+</verb></tscreen>
- if (new_value > dial->adjustment->upper)
- new_value = dial->adjustment->upper;
+which returns a GList (a linked list type from the glib library)
+of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined
+as:
- if (new_value != dial->adjustment->value)
- {
- dial->adjustment->value = new_value;
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
+<tscreen><verb>
+struct _GdkDeviceInfo
+{
+ guint32 deviceid;
+ gchar *name;
+ GdkInputSource source;
+ GdkInputMode mode;
+ gint has_cursor;
+ gint num_axes;
+ GdkAxisUse *axes;
+ gint num_keys;
+ GdkDeviceKey *keys;
+};
+</verb></tscreen>
- dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
- (dial->adjustment->upper - dial->adjustment->lower);
+Most of these fields are configuration information that you
+can ignore unless you are implemented XInput configuration
+saving. The we are interested in here is <tt/name/ which is
+simply the name that X assigns to the device. The other field
+that isn't configuration information is <tt/has_cursor/. If
+<tt/has_cursor/ is false, then we we need to draw our own
+cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/,
+we don't have to worry about this.
- gtk_widget_draw (GTK_WIDGET(dial), NULL);
-}
+<p>
+Our <tt/print_button_press()/ function simply iterates through
+the returned list until it finds a match, then prints out
+the name of the device.
+<tscreen><verb>
static void
-gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
- gpointer data)
+print_button_press (guint32 deviceid)
{
- GtkDial *dial;
-
- g_return_if_fail (adjustment != NULL);
- g_return_if_fail (data != NULL);
+ GList *tmp_list;
- dial = GTK_DIAL (data);
+ /* gdk_input_list_devices returns an internal list, so we shouldn't
+ free it afterwards */
+ tmp_list = gdk_input_list_devices();
- if ((dial->old_value != adjustment->value) ||
- (dial->old_lower != adjustment->lower) ||
- (dial->old_upper != adjustment->upper))
+ while (tmp_list)
{
- gtk_dial_update (dial);
+ GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
- dial->old_value = adjustment->value;
- dial->old_lower = adjustment->lower;
- dial->old_upper = adjustment->upper;
+ if (info->deviceid == deviceid)
+ {
+ printf("Button press on device '%s'\n", info->name);
+ return;
+ }
+
+ tmp_list = tmp_list->next;
}
}
+</verb></tscreen>
-static void
-gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
- gpointer data)
-{
- GtkDial *dial;
+That completes the changes to ``XInputize'' our program. As with
+the first version, the complete source is available at the location
+from which you got this tutorial, or from:
- g_return_if_fail (adjustment != NULL);
- g_return_if_fail (data != NULL);
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
- dial = GTK_DIAL (data);
- if (dial->old_value != adjustment->value)
- {
- gtk_dial_update (dial);
+<sect2> Further sophistications <label id="sec_Further_Sophistications">
- dial->old_value = adjustment->value;
- }
-}
-</verb></tscreen>
+<p>
+Although our program now supports XInput quite well, it lacks some
+features we would want in a full-featured application. First, the user
+probably doesn't want to have to configure their device each time they
+run the program, so we should allow them to save the device
+configuration. This is done by iterating through the return of
+<tt/gdk_input_list_devices()/ and writing out the configuration to a
+file.
-<!-- ----------------------------------------------------------------- -->
-<sect2> Possible Enhancements
<p>
+To restore the state next time the program is run, GDK provides
+functions to change device configuration:
-The Dial widget as we've described it so far runs about 670 lines of
-code. Although that might sound like a fair bit, we've really
-accomplished quite a bit with that much code, especially since much of
-that length is headers and boilerplate. However, there are quite a few
-more enhancements that could be made to this widget:
+<tscreen><verb>
+gdk_input_set_extension_events()
+gdk_input_set_source()
+gdk_input_set_mode()
+gdk_input_set_axes()
+gdk_input_set_key()
+</verb></tscreen>
-<itemize>
-<item> If you try this widget out, you'll find that there is some
-flashing as the pointer is dragged around. This is because the entire
-widget is erased every time the pointer is moved before being
-redrawn. Often, the best way to handle this problem is to draw to an
-offscreen pixmap, then copy the final results onto the screen in one
-step. (The ProgressBar widget draws itself in this fashion.)
+(The list returned from <tt/gdk_input_list_devices()/ should not be
+modified directly.) An example of doing this can be found in the
+drawing program gsumi. (Available from <htmlurl
+url="http://www.msc.cornell.edu/~otaylor/gsumi/"
+name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it
+would be nice to have a standard way of doing this for all
+applications. This probably belongs at a slightly higher level than
+GTK, perhaps in the GNOME library.
-<item> The user should be able to use the up and down arrow keys to
-increase and decrease the value.
+<p>
+Another major ommission that we have mentioned above is the lack of
+cursor drawing. Platforms other than XFree86 currently do not allow
+simultaneously using a device as both the core pointer and directly by
+an application. See the <url
+url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
+name="XInput-HOWTO"> for more information about this. This means that
+applications that want to support the widest audience need to draw
+their own cursor.
-<item> It would be nice if the widget had buttons to increase and
-decrease the value in small or large steps. Although it would be
-possible to use embedded Button widgets for this, we would also like
-the buttons to auto-repeat when held down, as the arrows on a
-scrollbar do. Most of the code to implement this type of behavior can
-be found in the GtkRange widget.
+<p>
+An application that draws it's own cursor needs to do two things:
+determine if the current device needs a cursor drawn or not, and
+determine if the current device is in proximity. (If the current
+device is a drawing tablet, it's a nice touch to make the cursor
+disappear when the stylus is lifted from the tablet. When the
+device is touching the stylus, that is called "in proximity.")
+The first is done by searching the device list, as we did
+to find out the device name. The second is achieved by selecting
+"proximity_out" events. An example of drawing one's own cursor is
+found in the 'testinput' program found in the GTK distribution.
-<item> The Dial widget could be made into a container widget with a
-single child widget positioned at the bottom between the buttons
-mentioned above. The user could then add their choice of a label or
-entry widget to display the current value of the dial.
+<!-- ***************************************************************** -->
+<sect>Tips For Writing GTK Applications
+<!-- ***************************************************************** -->
-</itemize>
+<p>
+This section is simply a gathering of wisdom, general style guidelines and hints to
+creating good GTK applications. It is totally useless right now cause it's
+only a topic sentence :)
-<!-- ----------------------------------------------------------------- -->
-<sect1> Learning More
+Use GNU autoconf and automake! They are your friends :) I am planning to
+make a quick intro on them here.
+
+<!-- ***************************************************************** -->
+<sect>Contributing
+<!-- ***************************************************************** -->
<p>
-Only a small part of the many details involved in creating widgets
-could be described above. If you want to write your own widgets, the
-best source of examples is the GTK source itself. Ask yourself some
-questions about the widget you want to write: is it a Container
-widget? does it have its own window? is it a modification of an
-existing widget? Then find a similar widget, and start making changes.
-Good luck!
+This document, like so much other great software out there, was created for
+free by volunteers. If you are at all knowledgeable about any aspect of GTK
+that does not already have documentation, please consider contributing to
+this document.
+<p>
+If you do decide to contribute, please mail your text to Tony Gale,
+<tt><htmlurl url="mailto:gale@gtk.org"
+name="gale@gtk.org"></tt>. Also, be aware that the entirety of this
+document is free, and any addition by yourself must also be free. That is,
+people may use any portion of your examples in their programs, and copies
+of this document may be distributed at will etc.
+<p>
+Thank you.
<!-- ***************************************************************** -->
-<sect>Scribble, A Simple Example Drawing Program
+<sect>Credits
<!-- ***************************************************************** -->
+<p>
+I would like to thank the following for their contributions to this text.
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
+<itemize>
+<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
+name="chamele0n@geocities.com"></tt> for the menus tutorial.
-<p>
-In this section, we will build a simple drawing program. In the
-process, we will examine how to handle mouse events, how to draw in a
-window, and how to do drawing better by using a backing pixmap. After
-creating the simple drawing program, we will extend it by adding
-support for XInput devices, such as drawing tablets. GTK provides
-support routines which makes getting extended information, such as
-pressure and tilt, from such devices quite easy.
+<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
+name="raph@acm.org"></tt>
+for hello world ala GTK, widget packing, and general all around wisdom.
+He's also generously donated a home for this tutorial.
-<!-- ----------------------------------------------------------------- -->
-<sect1> Event Handling
+<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
+name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program..
+and the ability to make it :)
-<p>
-The GTK signals we have already discussed are for high-level actions,
-such as a menu item being selected. However, sometimes it is useful to
-learn about lower-level occurrences, such as the mouse being moved, or
-a key being pressed. There are also GTK signals corresponding to these
-low-level <em>events</em>. The handlers for these signals have an
-extra parameter which is a pointer to a structure containing
-information about the event. For instance, motion events handlers are
-passed a pointer to a GdkEventMotion structure which looks (in part)
-like:
+<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
+name="werner.koch@guug.de"></tt> for converting the original plain text to
+SGML, and the widget class hierarchy.
-<tscreen><verb>
-struct _GdkEventMotion
-{
- GdkEventType type;
- GdkWindow *window;
- guint32 time;
- gdouble x;
- gdouble y;
- ...
- guint state;
- ...
-};
-</verb></tscreen>
+<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
+name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and
+the table packing tutorial.
-<tt/type/ will be set to the event type, in this case
-<tt/GDK_MOTION_NOTIFY/, window is the window in which the event
-occured. <tt/x/ and <tt/y/ give the coordinates of the event,
-and <tt/state/ specifies the modifier state when the event
-occurred (that is, it specifies which modifier keys and mouse buttons
-were pressed.) It is the bitwise OR of some of the following:
+<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
+name="owt1@cornell.edu"></tt> for the EventBox widget section (and
+the patch to the distro). He's also responsible for the selections code and
+tutorial, as well as the sections on writing your own GTK widgets, and the
+example application. Thanks a lot Owen for all you help!
-<tscreen><verb>
-GDK_SHIFT_MASK
-GDK_LOCK_MASK
-GDK_CONTROL_MASK
-GDK_MOD1_MASK
-GDK_MOD2_MASK
-GDK_MOD3_MASK
-GDK_MOD4_MASK
-GDK_MOD5_MASK
-GDK_BUTTON1_MASK
-GDK_BUTTON2_MASK
-GDK_BUTTON3_MASK
-GDK_BUTTON4_MASK
-GDK_BUTTON5_MASK
-</verb></tscreen>
+<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
+name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook,
+Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark!
+You've been a great help.
-<p>
-As for other signals, to determine what happens when an event occurs
-we call <tt>gtk_signal_connect()</tt>. But we also need let GTK
-know which events we want to be notified about. To do this, we call
-the function:
+<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
+name="timj@psynet.net"></tt> for his great job on the Lists Widget.
+Thanks Tim :)
-<tscreen><verb>
-void gtk_widget_set_events (GtkWidget *widget,
- gint events);
-</verb></tscreen>
+<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
+name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial.
-The second field specifies the events we are interested in. It
-is the bitwise OR of constants that specify different types
-of events. For future reference the event types are:
+<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
+name="johnsonm@redhat.com"></tt> for info and code for popup menus.
-<tscreen><verb>
-GDK_EXPOSURE_MASK
-GDK_POINTER_MOTION_MASK
-GDK_POINTER_MOTION_HINT_MASK
-GDK_BUTTON_MOTION_MASK
-GDK_BUTTON1_MOTION_MASK
-GDK_BUTTON2_MOTION_MASK
-GDK_BUTTON3_MOTION_MASK
-GDK_BUTTON_PRESS_MASK
-GDK_BUTTON_RELEASE_MASK
-GDK_KEY_PRESS_MASK
-GDK_KEY_RELEASE_MASK
-GDK_ENTER_NOTIFY_MASK
-GDK_LEAVE_NOTIFY_MASK
-GDK_FOCUS_CHANGE_MASK
-GDK_STRUCTURE_MASK
-GDK_PROPERTY_CHANGE_MASK
-GDK_PROXIMITY_IN_MASK
-GDK_PROXIMITY_OUT_MASK
-</verb></tscreen>
+</itemize>
+<p>
+And to all of you who commented and helped refine this document.
+<p>
+Thanks.
-There are a few subtle points that have to be observed when calling
-<tt/gtk_widget_set_events()/. First, it must be called before the X window
-for a GTK widget is created. In practical terms, this means you
-should call it immediately after creating the widget. Second, the
-widget must have an associated X window. For efficiency, many widget
-types do not have their own window, but draw in their parent's window.
-These widgets are:
+<!-- ***************************************************************** -->
+<sect> Tutorial Copyright and Permissions Notice
+<!-- ***************************************************************** -->
-<tscreen><verb>
-GtkAlignment
-GtkArrow
-GtkBin
-GtkBox
-GtkImage
-GtkItem
-GtkLabel
-GtkPixmap
-GtkScrolledWindow
-GtkSeparator
-GtkTable
-GtkAspectFrame
-GtkFrame
-GtkVBox
-GtkHBox
-GtkVSeparator
-GtkHSeparator
-</verb></tscreen>
+<p>
+The GTK Tutorial is Copyright (C) 1997 Ian Main.
-To capture events for these widgets, you need to use an EventBox
-widget. See the section on
-<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for
-details.
+Copyright (C) 1998 Tony Gale.
+<p>
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+<P>Permission is granted to copy and distribute modified versions of
+this document under the conditions for verbatim copying, provided that
+this copyright notice is included exactly as in the original,
+and that the entire resulting derived work is distributed under
+the terms of a permission notice identical to this one.
+<P>Permission is granted to copy and distribute translations of this
+document into another language, under the above conditions for modified
+versions.
+<P>If you are intending to incorporate this document into a published
+work, please contact the maintainer, and we will make an effort
+to ensure that you have the most up to date information available.
+<P>There is no guarentee that this document lives up to its intended
+purpose. This is simply provided as a free resource. As such,
+the authors and maintainers of the information provided within can
+not make any guarentee that the information is even accurate.
-<p>
-For our drawing program, we want to know when the mouse button is
-pressed and when the mouse is moved, so we specify
-<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also
-want to know when we need to redraw our window, so we specify
-<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a
-Configure event when our window size changes, we don't have to specify
-the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is
-automatically specified for all windows.
+<!-- ***************************************************************** -->
+<appendix>
+<!-- ***************************************************************** -->
+<!-- ***************************************************************** -->
+<sect> Code Examples
+<!-- ***************************************************************** -->
<p>
-It turns out, however, that there is a problem with just specifying
-<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new
-motion event to the event queue every time the user moves the mouse.
-Imagine that it takes us 0.1 seconds to handle a motion event, but the
-X server queues a new motion event every 0.05 seconds. We will soon
-get way behind the users drawing. If the user draws for 5 seconds,
-it will take us another 5 seconds to catch up after they release
-the mouse button! What we would like is to only get one motion
-event for each event we process. The way to do this is to
-specify <tt/GDK_POINTER_MOTION_HINT_MASK/.
+Below are the code examples that are used in the above text
+which are not included in complete form elsewhere.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Scribble
<p>
-When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends
-us a motion event the first time the pointer moves after entering
-our window, or after a button press or release event. Subsequent
-motion events will be suppressed until we explicitely ask for
-the position of the pointer using the function:
-
<tscreen><verb>
-GdkWindow* gdk_window_get_pointer (GdkWindow *window,
- gint *x,
- gint *y,
- GdkModifierType *mask);
-</verb></tscreen>
+/* example-start scribble-simple scribble-simple.c */
-(There is another function, <tt>gtk_widget_get_pointer()</tt> which
-has a simpler interface, but turns out not to be very useful, since
-it only retrieves the position of the mouse, not whether the buttons
-are pressed.)
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
-<p>
-The code to set the events for our window then looks like:
+#include <gtk/gtk.h>
-<tscreen><verb>
- gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
- (GtkSignalFunc) expose_event, NULL);
- gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
- (GtkSignalFunc) configure_event, NULL);
- gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
- (GtkSignalFunc) motion_notify_event, NULL);
- gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
- (GtkSignalFunc) button_press_event, NULL);
+/* Backing pixmap for drawing area */
+static GdkPixmap *pixmap = NULL;
- gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
- | GDK_LEAVE_NOTIFY_MASK
- | GDK_BUTTON_PRESS_MASK
- | GDK_POINTER_MOTION_MASK
- | GDK_POINTER_MOTION_HINT_MASK);
-</verb></tscreen>
+/* Create a new backing pixmap of the appropriate size */
+static gint
+configure_event (GtkWidget *widget, GdkEventConfigure *event)
+{
+ if (pixmap)
+ gdk_pixmap_unref(pixmap);
-We'll save the "expose_event" and "configure_event" handlers for
-later. The "motion_notify_event" and "button_press_event" handlers
-pretty simple:
+ pixmap = gdk_pixmap_new(widget->window,
+ widget->allocation.width,
+ widget->allocation.height,
+ -1);
+ gdk_draw_rectangle (pixmap,
+ widget->style->white_gc,
+ TRUE,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+
+ return TRUE;
+}
+
+/* Redraw the screen from the backing pixmap */
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *event)
+{
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+
+ return FALSE;
+}
+
+/* Draw a rectangle on the screen */
+static void
+draw_brush (GtkWidget *widget, gdouble x, gdouble y)
+{
+ GdkRectangle update_rect;
+
+ update_rect.x = x - 5;
+ update_rect.y = y - 5;
+ update_rect.width = 10;
+ update_rect.height = 10;
+ gdk_draw_rectangle (pixmap,
+ widget->style->black_gc,
+ TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
+}
-<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && pixmap != NULL)
- draw_brush (widget, event->x, event->y);
+ draw_brush (widget, event->x, event->y);
return TRUE;
}
return TRUE;
}
+
+void
+quit ()
+{
+ gtk_exit (0);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *drawing_area;
+ GtkWidget *vbox;
+
+ GtkWidget *button;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_name (window, "Test Input");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+ gtk_widget_show (vbox);
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (quit), NULL);
+
+ /* Create the drawing area */
+
+ drawing_area = gtk_drawing_area_new ();
+ gtk_drawing_area_size (GTK_DRAWING_AREA (drawing_area), 200, 200);
+ gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
+
+ gtk_widget_show (drawing_area);
+
+ /* Signals used to handle backing pixmap */
+
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
+ (GtkSignalFunc) expose_event, NULL);
+ gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
+ (GtkSignalFunc) configure_event, NULL);
+
+ /* Event signals */
+
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
+ (GtkSignalFunc) motion_notify_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
+ (GtkSignalFunc) button_press_event, NULL);
+
+ gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK);
+
+ /* .. And a quit button */
+ button = gtk_button_new_with_label ("Quit");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (gtk_widget_destroy),
+ GTK_OBJECT (window));
+ gtk_widget_show (button);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect1> The DrawingArea Widget, And Drawing
+<sect1> GtkDial
+<!-- ----------------------------------------------------------------- -->
+<sect2> gtkdial.h
<p>
-We know turn to the process of drawing on the screen. The
-widget we use for this is the DrawingArea widget. A drawing area
-widget is essentially an X window and nothing more. It is a blank
-canvas in which we can draw whatever we like. A drawing area
-is created using the call:
-
<tscreen><verb>
-GtkWidget* gtk_drawing_area_new (void);
-</verb></tscreen>
+/* example-start gtkdial gtkdial.h */
-A default size for the widget can be specified by calling:
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GTK_DIAL_H__
+#define __GTK_DIAL_H__
-<tscreen><verb>
-void gtk_drawing_area_size (GtkDrawingArea *darea,
- gint width,
- gint height);
-</verb></tscreen>
-This default size can be overriden, as is true for all widgets,
-by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can
-be overridden if the user manually resizes the the window containing
-the drawing area.
+#include <gdk/gdk.h>
+#include <gtk/gtkadjustment.h>
+#include <gtk/gtkwidget.h>
-<p>
-It should be noted that when we create a DrawingArea widget, we are,
-<em>completely</em> responsible for drawing the contents. If our
-window is obscured then uncovered, we get an exposure event and must
-redraw what was previously hidden.
-<p>
-Having to remember everything that was drawn on the screen so we
-can properly redraw it can, to say the least, be a nuisance. In
-addition, it can be visually distracting if portions of the
-window are cleared, then redrawn step by step. The solution to
-this problem is to use an offscreen <em>backing pixmap</em>.
-Instead of drawing directly to the screen, we draw to an image
-stored in server memory but not displayed, then when the image
-changes or new portions of the image are displayed, we copy the
-relevant portions onto the screen.
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
+#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
+#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
+
+
+typedef struct _GtkDial GtkDial;
+typedef struct _GtkDialClass GtkDialClass;
+
+struct _GtkDial
+{
+ GtkWidget widget;
+
+ /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
+ guint policy : 2;
+
+ /* Button currently pressed or 0 if none */
+ guint8 button;
-<p>
-To create an offscreen pixmap, we call the function:
+ /* Dimensions of dial components */
+ gint radius;
+ gint pointer_width;
-<tscreen><verb>
-GdkPixmap* gdk_pixmap_new (GdkWindow *window,
- gint width,
- gint height,
- gint depth);
-</verb></tscreen>
+ /* ID of update timer, or 0 if none */
+ guint32 timer;
-The <tt>window</tt> parameter specifies a GDK window that this pixmap
-takes some of its properties from. <tt>width</tt> and <tt>height</tt>
-specify the size of the pixmap. <tt>depth</tt> specifies the <em>color
-depth</em>, that is the number of bits per pixel, for the new window.
-If the depth is specified as <tt>-1</tt>, it will match the depth
-of <tt>window</tt>.
+ /* Current angle */
+ gfloat angle;
-<p>
-We create the pixmap in our "configure_event" handler. This event
-is generated whenever the window changes size, including when it
-is originally created.
+ /* Old values from adjustment stored so we know when something changes */
+ gfloat old_value;
+ gfloat old_lower;
+ gfloat old_upper;
-<tscreen><verb>
-/* Backing pixmap for drawing area */
-static GdkPixmap *pixmap = NULL;
+ /* The adjustment object that stores the data for this dial */
+ GtkAdjustment *adjustment;
+};
-/* Create a new backing pixmap of the appropriate size */
-static gint
-configure_event (GtkWidget *widget, GdkEventConfigure *event)
+struct _GtkDialClass
{
- if (pixmap)
- {
- gdk_pixmap_destroy(pixmap);
- }
- pixmap = gdk_pixmap_new(widget->window,
- widget->allocation.width,
- widget->allocation.height,
- -1);
- gdk_draw_rectangle (pixmap,
- widget->style->white_gc,
- TRUE,
- 0, 0,
- widget->allocation.width,
- widget->allocation.height);
+ GtkWidgetClass parent_class;
+};
- return TRUE;
-}
-</verb></tscreen>
-The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap
-initially to white. We'll say more about that in a moment.
+GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
+guint gtk_dial_get_type (void);
+GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
+void gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy);
-<p>
-Our exposure event handler then simply copies the relevant portion
-of the pixmap onto the screen (we determine the area we need
-to redraw by using the event->area field of the exposure event):
+void gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-<tscreen><verb>
-/* Refill the screen from the backing pixmap */
-static gint
-expose_event (GtkWidget *widget, GdkEventExpose *event)
-{
- gdk_draw_pixmap(widget->window,
- widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
- pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- return FALSE;
-}
+#endif /* __GTK_DIAL_H__ */
+/* example-end */
</verb></tscreen>
-We've now seen how to keep the screen up to date with our pixmap, but
-how do we actually draw interesting stuff on our pixmap? There are a
-large number of calls in GTK's GDK library for drawing on
-<em>drawables</em>. A drawable is simply something that can be drawn
-upon. It can be a window, a pixmap, or a bitmap (a black and white
-image). We've already seen two such calls above,
-<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The
-complete list is:
-
+<!-- ----------------------------------------------------------------- -->
+<sect2> gtkdial.c
+<p>
<tscreen><verb>
-gdk_draw_line ()
-gdk_draw_rectangle ()
-gdk_draw_arc ()
-gdk_draw_polygon ()
-gdk_draw_string ()
-gdk_draw_text ()
-gdk_draw_pixmap ()
-gdk_draw_bitmap ()
-gdk_draw_image ()
-gdk_draw_points ()
-gdk_draw_segments ()
-</verb></tscreen>
+/* example-start gtkdial gtkdial.c */
-See the reference documentation or the header file
-<tt><gdk/gdk.h></tt> for further details on these functions.
-These functions all share the same first two arguments. The first
-argument is the drawable to draw upon, the second argument is a
-<em>graphics context</em> (GC).
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include <math.h>
+#include <stdio.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
-<p>
-A graphics context encapsulates information about things such as
-foreground and background color and line width. GDK has a full set of
-functions for creating and modifying graphics contexts, but to keep
-things simple we'll just use predefined graphics contexts. Each widget
-has an associated style. (Which can be modified in a gtkrc file, see
-the section GTK's rc file.) This, among other things, stores a number
-of graphics contexts. Some examples of accessing these graphics
-contexts are:
+#include "gtkdial.h"
-<tscreen><verb>
-widget->style->white_gc
-widget->style->black_gc
-widget->style->fg_gc[GTK_STATE_NORMAL]
-widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
-</verb></tscreen>
+#define SCROLL_DELAY_LENGTH 300
+#define DIAL_DEFAULT_SIZE 100
-The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and
-<tt>light_gc</tt> are indexed by a parameter of type
-<tt>GtkStateType</tt> which can take on the values:
+/* Forward declararations */
-<tscreen><verb>
-GTK_STATE_NORMAL,
-GTK_STATE_ACTIVE,
-GTK_STATE_PRELIGHT,
-GTK_STATE_SELECTED,
-GTK_STATE_INSENSITIVE
-</verb></tscreen>
+static void gtk_dial_class_init (GtkDialClass *klass);
+static void gtk_dial_init (GtkDial *dial);
+static void gtk_dial_destroy (GtkObject *object);
+static void gtk_dial_realize (GtkWidget *widget);
+static void gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition);
+static void gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gint gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event);
+static gint gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+static gint gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event);
+static gint gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event);
+static gint gtk_dial_timer (GtkDial *dial);
+
+static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y);
+static void gtk_dial_update (GtkDial *dial);
+static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data);
+static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data);
-For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground
-color is white and the default background color, dark blue.
+/* Local data */
-<p>
-Our function <tt>draw_brush()</tt>, which does the actual drawing
-on the screen, is then:
+static GtkWidgetClass *parent_class = NULL;
-<tscreen><verb>
-/* Draw a rectangle on the screen */
-static void
-draw_brush (GtkWidget *widget, gdouble x, gdouble y)
+guint
+gtk_dial_get_type ()
{
- GdkRectangle update_rect;
+ static guint dial_type = 0;
- update_rect.x = x - 5;
- update_rect.y = y - 5;
- update_rect.width = 10;
- update_rect.height = 10;
- gdk_draw_rectangle (pixmap,
- widget->style->black_gc,
- TRUE,
- update_rect.x, update_rect.y,
- update_rect.width, update_rect.height);
- gtk_widget_draw (widget, &update_rect);
-}
-</verb></tscreen>
+ if (!dial_type)
+ {
+ GtkTypeInfo dial_info =
+ {
+ "GtkDial",
+ sizeof (GtkDial),
+ sizeof (GtkDialClass),
+ (GtkClassInitFunc) gtk_dial_class_init,
+ (GtkObjectInitFunc) gtk_dial_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL,
+ };
-After we draw the rectangle representing the brush onto the pixmap,
-we call the function:
+ dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
+ }
-<tscreen><verb>
-void gtk_widget_draw (GtkWidget *widget,
- GdkRectangle *area);
-</verb></tscreen>
+ return dial_type;
+}
-which notifies X that the area given by the <tt>area</tt> parameter
-needs to be updated. X will eventually generate an expose event
-(possibly combining the areas passed in several calls to
-<tt>gtk_widget_draw()</tt>) which will cause our expose event handler
-to copy the relevant portions to the screen.
+static void
+gtk_dial_class_init (GtkDialClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
-<p>
-We have now covered the entire drawing program except for a few
-mundane details like creating the main window. The complete
-source code is available from the location from which you got
-this tutorial, or from:
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+ parent_class = gtk_type_class (gtk_widget_get_type ());
+ object_class->destroy = gtk_dial_destroy;
-<!-- ----------------------------------------------------------------- -->
-<sect1> Adding XInput support
+ widget_class->realize = gtk_dial_realize;
+ widget_class->expose_event = gtk_dial_expose;
+ widget_class->size_request = gtk_dial_size_request;
+ widget_class->size_allocate = gtk_dial_size_allocate;
+ widget_class->button_press_event = gtk_dial_button_press;
+ widget_class->button_release_event = gtk_dial_button_release;
+ widget_class->motion_notify_event = gtk_dial_motion_notify;
+}
+
+static void
+gtk_dial_init (GtkDial *dial)
+{
+ dial->button = 0;
+ dial->policy = GTK_UPDATE_CONTINUOUS;
+ dial->timer = 0;
+ dial->radius = 0;
+ dial->pointer_width = 0;
+ dial->angle = 0.0;
+ dial->old_value = 0.0;
+ dial->old_lower = 0.0;
+ dial->old_upper = 0.0;
+ dial->adjustment = NULL;
+}
-<p>
+GtkWidget*
+gtk_dial_new (GtkAdjustment *adjustment)
+{
+ GtkDial *dial;
-It is now possible to buy quite inexpensive input devices such
-as drawing tablets, which allow drawing with a much greater
-ease of artistic expression than does a mouse. The simplest way
-to use such devices is simply as a replacement for the mouse,
-but that misses out many of the advantages of these devices,
-such as:
+ dial = gtk_type_new (gtk_dial_get_type ());
-<itemize>
-<item> Pressure sensitivity
-<item> Tilt reporting
-<item> Sub-pixel positioning
-<item> Multiple inputs (for example, a stylus with a point and eraser)
-</itemize>
+ if (!adjustment)
+ adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-For information about the XInput extension, see the <htmlurl
-url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
-name="XInput-HOWTO">.
+ gtk_dial_set_adjustment (dial, adjustment);
-<p>
-If we examine the full definition of, for example, the GdkEventMotion
-structure, we see that it has fields to support extended device
-information.
+ return GTK_WIDGET (dial);
+}
-<tscreen><verb>
-struct _GdkEventMotion
+static void
+gtk_dial_destroy (GtkObject *object)
{
- GdkEventType type;
- GdkWindow *window;
- guint32 time;
- gdouble x;
- gdouble y;
- gdouble pressure;
- gdouble xtilt;
- gdouble ytilt;
- guint state;
- gint16 is_hint;
- GdkInputSource source;
- guint32 deviceid;
-};
-</verb></tscreen>
-
-<tt/pressure/ gives the pressure as a floating point number between
-0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between
--1 and 1, corresponding to the degree of tilt in each direction.
-<tt/source/ and <tt/deviceid/ specify the device for which the
-event occurred in two different ways. <tt/source/ gives some simple
-information about the type of device. It can take the enumeration
-values.
-
-<tscreen><verb>
-GDK_SOURCE_MOUSE
-GDK_SOURCE_PEN
-GDK_SOURCE_ERASER
-GDK_SOURCE_CURSOR
-</verb></tscreen>
+ GtkDial *dial;
-<tt/deviceid/ specifies a unique numeric ID for the device. This can
-be used to find out further information about the device using the
-<tt/gdk_input_list_devices()/ call (see below). The special value
-<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually
-the mouse.)
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_DIAL (object));
-<sect2> Enabling extended device information
+ dial = GTK_DIAL (object);
-<p>
-To let GTK know about our interest in the extended device information,
-we merely have to add a single line to our program:
+ if (dial->adjustment)
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
-<tscreen><verb>
-gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
-</verb></tscreen>
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
-By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that
-we are interested in extension events, but only if we don't have
-to draw our own cursor. See the section <ref
-id="sec_Further_Sophistications" name="Further Sophistications"> below
-for more information about drawing the cursor. We could also
-give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing
-to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert
-back to the default condition.
+GtkAdjustment*
+gtk_dial_get_adjustment (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-<p>
-This is not completely the end of the story however. By default,
-no extension devices are enabled. We need a mechanism to allow
-users to enable and configure their extension devices. GTK provides
-the InputDialog widget to automate this process. The following
-procedure manages an InputDialog widget. It creates the dialog if
-it isn't present, and raises it to the top otherwise.
+ return dial->adjustment;
+}
-<tscreen><verb>
void
-input_dialog_destroy (GtkWidget *w, gpointer data)
+gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy)
{
- *((GtkWidget **)data) = NULL;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
+
+ dial->policy = policy;
}
void
-create_input_dialog ()
+gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment)
{
- static GtkWidget *inputd = NULL;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
- if (!inputd)
+ if (dial->adjustment)
{
- inputd = gtk_input_dialog_new();
+ gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
+ }
- gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
- (GtkSignalFunc)input_dialog_destroy, &inputd);
- gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
- "clicked",
- (GtkSignalFunc)gtk_widget_hide,
- GTK_OBJECT(inputd));
- gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
+ dial->adjustment = adjustment;
+ gtk_object_ref (GTK_OBJECT (dial->adjustment));
- gtk_widget_show (inputd);
- }
- else
- {
- if (!GTK_WIDGET_MAPPED(inputd))
- gtk_widget_show(inputd);
- else
- gdk_window_raise(inputd->window);
- }
+ gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
+ (GtkSignalFunc) gtk_dial_adjustment_changed,
+ (gpointer) dial);
+ gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
+ (GtkSignalFunc) gtk_dial_adjustment_value_changed,
+ (gpointer) dial);
+
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+
+ gtk_dial_update (dial);
}
-</verb></tscreen>
-(You might want to take note of the way we handle this dialog. By
-connecting to the "destroy" signal, we make sure that we don't keep a
-pointer to dialog around after it is destroyed - that could lead to a
-segfault.)
+static void
+gtk_dial_realize (GtkWidget *widget)
+{
+ GtkDial *dial;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
-<p>
-The InputDialog has two buttons "Close" and "Save", which by default
-have no actions assigned to them. In the above function we make
-"Close" hide the dialog, hide the "Save" button, since we don't
-implement saving of XInput options in this program.
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
-<sect2> Using extended device information
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+ dial = GTK_DIAL (widget);
-<p>
-Once we've enabled the device, we can just use the extended
-device information in the extra fields of the event structures.
-In fact, it is always safe to use this information since these
-fields will have reasonable default values even when extended
-events are not enabled.
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
-<p>
-Once change we do have to make is to call
-<tt/gdk_input_window_get_pointer()/ instead of
-<tt/gdk_window_get_pointer/. This is necessary because
-<tt/gdk_window_get_pointer/ doesn't return the extended device
-information.
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+ widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
-<tscreen><verb>
-void gdk_input_window_get_pointer (GdkWindow *window,
- guint32 deviceid,
- gdouble *x,
- gdouble *y,
- gdouble *pressure,
- gdouble *xtilt,
- gdouble *ytilt,
- GdkModifierType *mask);
-</verb></tscreen>
+ widget->style = gtk_style_attach (widget->style, widget->window);
-When calling this function, we need to specify the device ID as
-well as the window. Usually, we'll get the device ID from the
-<tt/deviceid/ field of an event structure. Again, this function
-will return reasonable values when extension events are not
-enabled. (In this case, <tt/event->deviceid/ will have the value
-<tt/GDK_CORE_POINTER/).
+ gdk_window_set_user_data (widget->window, widget);
-So the basic structure of our button-press and motion event handlers,
-doesn't change much - we just need to add code to deal with the
-extended information.
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
+}
+
+static void
+gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = DIAL_DEFAULT_SIZE;
+ requisition->height = DIAL_DEFAULT_SIZE;
+}
+
+static void
+gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkDial *dial;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
+ g_return_if_fail (allocation != NULL);
+
+ widget->allocation = *allocation;
+ dial = GTK_DIAL (widget);
+
+ if (GTK_WIDGET_REALIZED (widget))
+ {
+
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ }
+ dial->radius = MIN(allocation->width,allocation->height) * 0.45;
+ dial->pointer_width = dial->radius / 5;
+}
-<tscreen><verb>
static gint
-button_press_event (GtkWidget *widget, GdkEventButton *event)
+gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event)
{
- print_button_press (event->deviceid);
+ GtkDial *dial;
+ GdkPoint points[3];
+ gdouble s,c;
+ gdouble theta;
+ gint xc, yc;
+ gint tick_length;
+ gint i;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ if (event->count > 0)
+ return FALSE;
- if (event->button == 1 && pixmap != NULL)
- draw_brush (widget, event->source, event->x, event->y, event->pressure);
+ dial = GTK_DIAL (widget);
- return TRUE;
-}
+ gdk_window_clear_area (widget->window,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
-static gint
-motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
-{
- gdouble x, y;
- gdouble pressure;
- GdkModifierType state;
+ xc = widget->allocation.width/2;
+ yc = widget->allocation.height/2;
- if (event->is_hint)
- gdk_input_window_get_pointer (event->window, event->deviceid,
- &x, &y, &pressure, NULL, NULL, &state);
- else
+ /* Draw ticks */
+
+ for (i=0; i<25; i++)
{
- x = event->x;
- y = event->y;
- pressure = event->pressure;
- state = event->state;
+ theta = (i*M_PI/18. - M_PI/6.);
+ s = sin(theta);
+ c = cos(theta);
+
+ tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
+
+ gdk_draw_line (widget->window,
+ widget->style->fg_gc[widget->state],
+ xc + c*(dial->radius - tick_length),
+ yc - s*(dial->radius - tick_length),
+ xc + c*dial->radius,
+ yc - s*dial->radius);
}
-
- if (state & GDK_BUTTON1_MASK && pixmap != NULL)
- draw_brush (widget, event->source, x, y, pressure);
+
+ /* Draw pointer */
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+
+ points[0].x = xc + s*dial->pointer_width/2;
+ points[0].y = yc + c*dial->pointer_width/2;
+ points[1].x = xc + c*dial->radius;
+ points[1].y = yc - s*dial->radius;
+ points[2].x = xc - s*dial->pointer_width/2;
+ points[2].y = yc - c*dial->pointer_width/2;
+
+ gtk_draw_polygon (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ points, 3,
+ TRUE);
- return TRUE;
+ return FALSE;
}
-</verb></tscreen>
-
-We also need to do something with the new information. Our new
-<tt/draw_brush()/ function draws with a different color for
-each <tt/event->source/ and changes the brush size depending
-on the pressure.
-<tscreen><verb>
-/* Draw a rectangle on the screen, size depending on pressure,
- and color on the type of device */
-static void
-draw_brush (GtkWidget *widget, GdkInputSource source,
- gdouble x, gdouble y, gdouble pressure)
+static gint
+gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event)
{
- GdkGC *gc;
- GdkRectangle update_rect;
+ GtkDial *dial;
+ gint dx, dy;
+ double s, c;
+ double d_parallel;
+ double d_perpendicular;
- switch (source)
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ /* Determine if button press was within pointer region - we
+ do this by computing the parallel and perpendicular distance of
+ the point where the mouse was pressed from the line passing through
+ the pointer */
+
+ dx = event->x - widget->allocation.width / 2;
+ dy = widget->allocation.height / 2 - event->y;
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+ d_parallel = s*dy + c*dx;
+ d_perpendicular = fabs(s*dx - c*dy);
+
+ if (!dial->button &&
+ (d_perpendicular < dial->pointer_width/2) &&
+ (d_parallel > - dial->pointer_width))
{
- case GDK_SOURCE_MOUSE:
- gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
- break;
- case GDK_SOURCE_PEN:
- gc = widget->style->black_gc;
- break;
- case GDK_SOURCE_ERASER:
- gc = widget->style->white_gc;
- break;
- default:
- gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
+ gtk_grab_add (widget);
+
+ dial->button = event->button;
+
+ gtk_dial_update_mouse (dial, event->x, event->y);
}
- update_rect.x = x - 10 * pressure;
- update_rect.y = y - 10 * pressure;
- update_rect.width = 20 * pressure;
- update_rect.height = 20 * pressure;
- gdk_draw_rectangle (pixmap, gc, TRUE,
- update_rect.x, update_rect.y,
- update_rect.width, update_rect.height);
- gtk_widget_draw (widget, &update_rect);
+ return FALSE;
}
-</verb></tscreen>
-<sect2> Finding out more about a device
+static gint
+gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkDial *dial;
-<p>
-As an example of how to find out more about a device, our program
-will print the name of the device that generates each button
-press. To find out the name of a device, we call the function:
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
-<tscreen><verb>
-GList *gdk_input_list_devices (void);
-</verb></tscreen>
+ dial = GTK_DIAL (widget);
-which returns a GList (a linked list type from the glib library)
-of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined
-as:
+ if (dial->button == event->button)
+ {
+ gtk_grab_remove (widget);
-<tscreen><verb>
-struct _GdkDeviceInfo
-{
- guint32 deviceid;
- gchar *name;
- GdkInputSource source;
- GdkInputMode mode;
- gint has_cursor;
- gint num_axes;
- GdkAxisUse *axes;
- gint num_keys;
- GdkDeviceKey *keys;
-};
-</verb></tscreen>
+ dial->button = 0;
-Most of these fields are configuration information that you
-can ignore unless you are implemented XInput configuration
-saving. The we are interested in here is <tt/name/ which is
-simply the name that X assigns to the device. The other field
-that isn't configuration information is <tt/has_cursor/. If
-<tt/has_cursor/ is false, then we we need to draw our own
-cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/,
-we don't have to worry about this.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_timeout_remove (dial->timer);
+
+ if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
+ (dial->old_value != dial->adjustment->value))
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
-<p>
-Our <tt/print_button_press()/ function simply iterates through
-the returned list until it finds a match, then prints out
-the name of the device.
+ return FALSE;
+}
-<tscreen><verb>
-static void
-print_button_press (guint32 deviceid)
+static gint
+gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
{
- GList *tmp_list;
+ GtkDial *dial;
+ GdkModifierType mods;
+ gint x, y, mask;
- /* gdk_input_list_devices returns an internal list, so we shouldn't
- free it afterwards */
- tmp_list = gdk_input_list_devices();
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
- while (tmp_list)
+ dial = GTK_DIAL (widget);
+
+ if (dial->button != 0)
{
- GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
+ x = event->x;
+ y = event->y;
- if (info->deviceid == deviceid)
+ if (event->is_hint || (event->window != widget->window))
+ gdk_window_get_pointer (widget->window, &x, &y, &mods);
+
+ switch (dial->button)
{
- printf("Button press on device '%s'\n", info->name);
- return;
+ case 1:
+ mask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ mask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ mask = GDK_BUTTON3_MASK;
+ break;
+ default:
+ mask = 0;
+ break;
}
- tmp_list = tmp_list->next;
+ if (mods & mask)
+ gtk_dial_update_mouse (dial, x,y);
}
+
+ return FALSE;
}
-</verb></tscreen>
-That completes the changes to ``XInputize'' our program. As with
-the first version, the complete source is available at the location
-from which you got this tutorial, or from:
+static gint
+gtk_dial_timer (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ return FALSE;
+}
-<sect2> Further sophistications <label id="sec_Further_Sophistications">
+static void
+gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+{
+ gint xc, yc;
+ gfloat old_value;
-<p>
-Although our program now supports XInput quite well, it lacks some
-features we would want in a full-featured application. First, the user
-probably doesn't want to have to configure their device each time they
-run the program, so we should allow them to save the device
-configuration. This is done by iterating through the return of
-<tt/gdk_input_list_devices()/ and writing out the configuration to a
-file.
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-<p>
-To restore the state next time the program is run, GDK provides
-functions to change device configuration:
+ xc = GTK_WIDGET(dial)->allocation.width / 2;
+ yc = GTK_WIDGET(dial)->allocation.height / 2;
-<tscreen><verb>
-gdk_input_set_extension_events()
-gdk_input_set_source()
-gdk_input_set_mode()
-gdk_input_set_axes()
-gdk_input_set_key()
-</verb></tscreen>
+ old_value = dial->adjustment->value;
+ dial->angle = atan2(yc-y, x-xc);
-(The list returned from <tt/gdk_input_list_devices()/ should not be
-modified directly.) An example of doing this can be found in the
-drawing program gsumi. (Available from <htmlurl
-url="http://www.msc.cornell.edu/~otaylor/gsumi/"
-name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it
-would be nice to have a standard way of doing this for all
-applications. This probably belongs at a slightly higher level than
-GTK, perhaps in the GNOME library.
+ if (dial->angle < -M_PI/2.)
+ dial->angle += 2*M_PI;
-<p>
-Another major ommission that we have mentioned above is the lack of
-cursor drawing. Platforms other than XFree86 currently do not allow
-simultaneously using a device as both the core pointer and directly by
-an application. See the <url
-url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
-name="XInput-HOWTO"> for more information about this. This means that
-applications that want to support the widest audience need to draw
-their own cursor.
+ if (dial->angle < -M_PI/6)
+ dial->angle = -M_PI/6;
-<p>
-An application that draws it's own cursor needs to do two things:
-determine if the current device needs a cursor drawn or not, and
-determine if the current device is in proximity. (If the current
-device is a drawing tablet, it's a nice touch to make the cursor
-disappear when the stylus is lifted from the tablet. When the
-device is touching the stylus, that is called "in proximity.")
-The first is done by searching the device list, as we did
-to find out the device name. The second is achieved by selecting
-"proximity_out" events. An example of drawing one's own cursor is
-found in the 'testinput' program found in the GTK distribution.
+ if (dial->angle > 7.*M_PI/6.)
+ dial->angle = 7.*M_PI/6.;
-<!-- ***************************************************************** -->
-<sect>Tips For Writing GTK Applications
-<!-- ***************************************************************** -->
+ dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
+ (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
-<p>
-This section is simply a gathering of wisdom, general style guidelines and hints to
-creating good GTK applications. It is totally useless right now cause it's
-only a topic sentence :)
+ if (dial->adjustment->value != old_value)
+ {
+ if (dial->policy == GTK_UPDATE_CONTINUOUS)
+ {
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ else
+ {
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
-Use GNU autoconf and automake! They are your friends :) I am planning to
-make a quick intro on them here.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ {
+ if (dial->timer)
+ gtk_timeout_remove (dial->timer);
-<!-- ***************************************************************** -->
-<sect>Contributing
-<!-- ***************************************************************** -->
+ dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
+ (GtkFunction) gtk_dial_timer,
+ (gpointer) dial);
+ }
+ }
+ }
+}
-<p>
-This document, like so much other great software out there, was created for
-free by volunteers. If you are at all knowledgeable about any aspect of GTK
-that does not already have documentation, please consider contributing to
-this document.
-<p>
-If you do decide to contribute, please mail your text to Tony Gale,
-<tt><htmlurl url="mailto:gale@gtk.org"
-name="gale@gtk.org"></tt>. Also, be aware that the entirety of this
-document is free, and any addition by yourself must also be free. That is,
-people may use any portion of your examples in their programs, and copies
-of this document may be distributed at will etc.
-<p>
-Thank you.
+static void
+gtk_dial_update (GtkDial *dial)
+{
+ gfloat new_value;
+
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-<!-- ***************************************************************** -->
-<sect>Credits
-<!-- ***************************************************************** -->
-<p>
-I would like to thank the following for their contributions to this text.
+ new_value = dial->adjustment->value;
+
+ if (new_value < dial->adjustment->lower)
+ new_value = dial->adjustment->lower;
-<itemize>
-<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
-name="chamele0n@geocities.com"></tt> for the menus tutorial.
+ if (new_value > dial->adjustment->upper)
+ new_value = dial->adjustment->upper;
-<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
-name="raph@acm.org"></tt>
-for hello world ala GTK, widget packing, and general all around wisdom.
-He's also generously donated a home for this tutorial.
+ if (new_value != dial->adjustment->value)
+ {
+ dial->adjustment->value = new_value;
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
-<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
-name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program..
-and the ability to make it :)
+ dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
+ (dial->adjustment->upper - dial->adjustment->lower);
-<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
-name="werner.koch@guug.de"></tt> for converting the original plain text to
-SGML, and the widget class hierarchy.
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
+}
-<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
-name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and
-the table packing tutorial.
+static void
+gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
-name="owt1@cornell.edu"></tt> for the EventBox widget section (and
-the patch to the distro). He's also responsible for the selections code and
-tutorial, as well as the sections on writing your own GTK widgets, and the
-example application. Thanks a lot Owen for all you help!
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
-<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
-name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook,
-Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark!
-You've been a great help.
+ dial = GTK_DIAL (data);
-<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
-name="timj@psynet.net"></tt> for his great job on the Lists Widget.
-Thanks Tim :)
+ if ((dial->old_value != adjustment->value) ||
+ (dial->old_lower != adjustment->lower) ||
+ (dial->old_upper != adjustment->upper))
+ {
+ gtk_dial_update (dial);
-<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
-name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial.
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ }
+}
-<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
-name="johnsonm@redhat.com"></tt> for info and code for popup menus.
+static void
+gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-</itemize>
-<p>
-And to all of you who commented and helped refine this document.
-<p>
-Thanks.
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
-<!-- ***************************************************************** -->
-<sect> Tutorial Copyright and Permissions Notice
-<!-- ***************************************************************** -->
+ dial = GTK_DIAL (data);
-<p>
-The GTK Tutorial is Copyright (C) 1997 Ian Main.
+ if (dial->old_value != adjustment->value)
+ {
+ gtk_dial_update (dial);
-Copyright (C) 1998 Tony Gale.
-<p>
-Permission is granted to make and distribute verbatim copies of this
-manual provided the copyright notice and this permission notice are
-preserved on all copies.
-<P>Permission is granted to copy and distribute modified versions of
-this document under the conditions for verbatim copying, provided that
-this copyright notice is included exactly as in the original,
-and that the entire resulting derived work is distributed under
-the terms of a permission notice identical to this one.
-<P>Permission is granted to copy and distribute translations of this
-document into another language, under the above conditions for modified
-versions.
-<P>If you are intending to incorporate this document into a published
-work, please contact the maintainer, and we will make an effort
-to ensure that you have the most up to date information available.
-<P>There is no guarentee that this document lives up to its intended
-purpose. This is simply provided as a free resource. As such,
-the authors and maintainers of the information provided within can
-not make any guarentee that the information is even accurate.
+ dial->old_value = adjustment->value;
+ }
+}
+/* example-end */
+</verb></tscreen>
</article>
name="<imain@gtk.org>"></tt>,
Tony Gale <tt><htmlurl url="mailto:gale@gtk.org"
name="<gale@gtk.org>"></tt>
-<date>June 24th, 1998
+<date>July 25th, 1998
<!-- ***************************************************************** -->
<sect>Introduction
GTK uses GNU autoconf for
configuration. Once untar'd, type ./configure --help to see a list of options.
+Th GTK source distribution also contains the complete source to all of the
+examples used in this tutorial, along with Makefiles to aid compilation.
+
To begin our introduction to GTK, we'll start with the simplest program
possible. This program will
create a 200x200 pixel window and has no way of exiting except to be
will output the list of libraries for the compiler to link with and
the directories to find them in.
+Note that the type of single quote used in the compile command above
+is significant.
+
The libraries that are usually linked in are:
<itemize>
<item>The GTK library (-lgtk), the widget library, based on top of GDK.
<tscreen><verb>
/* example-start packbox packbox.c */
+#include <stdio.h>
#include "gtk/gtk.h"
void
GtkListItem as well.
<!-- ***************************************************************** -->
-<sect>Menu Widgets
+<sect> Tree Widget<label id="sec_Tree_Widgets">
<!-- ***************************************************************** -->
<p>
-There are two ways to create menus, there's the easy way, and there's the
-hard way. Both have their uses, but you can usually use the menufactory
-(the easy way). The "hard" way is to create all the menus using the calls
-directly. The easy way is to use the gtk_menu_factory calls. This is
-much simpler, but there are advantages and disadvantages to each approach.
-The menufactory is much easier to use, and to add new menus to, although
-writing a few wrapper functions to create menus using the manual method
-could go a long way towards usability. With the menufactory, it is not
-possible to add images or the character '/' to the menus.
+The purpose of tree widgets is to display hierarchically-organized
+data. The GtkTree widget itself is a vertical container for widgets
+of type GtkTreeItem. GtkTree itself is not terribly different from
+GtkList - both are derived directly from GtkContainer, and the
+GtkContainer methods work in the same way on GtkTree widgets as on
+GtkList widgets. The difference is that GtkTree widgets can be nested
+within other GtkTree widgets. We'll see how to do this shortly.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Manual Menu Creation
+The GtkTree widget has its own window, and defaults to a white
+background, as does GtkList. Also, most of the GtkTree methods work
+in the same way as the corresponding GtkList ones. However, GtkTree
+is not derived from GtkList, so you cannot use them interchangeably.
+
+<sect1> Creating a Tree
<p>
-In the true tradition of teaching, we'll show you the hard
-way first. <tt>:)</>
+A GtkTree is created in the usual way, using:
-There are three widgets that go into making a menubar and submenus:
-<itemize>
-<item>a menu item, which is what the user wants to select, e.g. 'Save'
-<item>a menu, which acts as a container for the menu items, and
-<item>a menubar, which is a container for each of the individual menus,
-</itemize>
+<tscreen><verb>
+GtkWidget* gtk_tree_new( void );
+</verb></tscreen>
-This is slightly complicated by the fact that menu item widgets are used
-for two different things. They are both the widets that are packed into
-the menu, and the widget that is packed into the menubar, which,
-when selected, activiates the menu.
+Like the GtkList widget, a GtkTree will simply keep growing as more
+items are added to it, as well as when subtrees are expanded.
+For this reason, they are almost always packed into a
+GtkScrolledWindow. You might want to use gtk_widget_set_usize() on
+the scrolled window to ensure that it is big enough to see the tree's
+items, as the default size for GtkScrolledWindow is quite small.
-Let's look at the functions that are used to create menus and menubars.
-This first function is used to create a new menubar.
+Now that you have a tree, you'll probably want to add some items to
+it. <ref id="sec_Tree_Item_Widget" name="The Tree Item Widget"> below
+explains the gory details of GtkTreeItem. For now, it'll suffice to
+create one, using:
<tscreen><verb>
-GtkWidget *gtk_menu_bar_new( void );
+GtkWidget* gtk_tree_item_new_with_label( gchar *label );
</verb></tscreen>
-This rather self explanatory function creates a new menubar. You use
-gtk_container_add to pack this into a window, or the box_pack functions to
-pack it into a box - the same as buttons.
+You can then add it to the tree using one of the following (see
+<ref id="sec_GtkTree_Functions" name="Functions and Macros">
+below for more options):
<tscreen><verb>
-GtkWidget *gtk_menu_new( void );
+void gtk_tree_append( GtkTree *tree,
+ GtkWidget *tree_item );
+
+void gtk_tree_prepend( GtkTree *tree,
+ GtkWidget *tree_item );
</verb></tscreen>
-This function returns a pointer to a new menu, it is never actually shown
-(with gtk_widget_show), it is just a container for the menu items. Hopefully this will
-become more clear when you look at the example below.
+Note that you must add items to a GtkTree one at a time - there is no
+equivalent to gtk_list_*_items().
-The next two calls are used to create menu items that are packed into
-the menu (and menubar).
+<sect1> Adding a Subtree
+<p>
+A subtree is created like any other GtkTree widget. A subtree is added
+to another tree beneath a tree item, using:
<tscreen><verb>
-GtkWidget *gtk_menu_item_new( void );
+void gtk_tree_item_set_subtree( GtkTreeItem *tree_item,
+ GtkWidget *subtree );
</verb></tscreen>
-and
+You do not need to call gtk_widget_show() on a subtree before or after
+adding it to a GtkTreeItem. However, you <em>must</em> have added the
+GtkTreeItem in question to a parent tree before calling
+gtk_tree_item_set_subtree(). This is because, technically, the parent
+of the subtree is <em>not</em> the GtkTreeItem which "owns" it, but
+rather the GtkTree which holds that GtkTreeItem.
+
+When you add a subtree to a GtkTreeItem, a plus or minus sign appears
+beside it, which the user can click on to "expand" or "collapse" it,
+meaning, to show or hide its subtree. GtkTreeItems are collapsed by
+default. Note that when you collapse a GtkTreeItem, any selected
+items in its subtree remain selected, which may not be what the user
+expects.
+
+<sect1> Handling the Selection List
+<p>
+As with GtkList, the GtkTree type has a <tt>selection</tt> field, and
+it is possible to control the behaviour of the tree (somewhat) by
+setting the selection type using:
<tscreen><verb>
-GtkWidget *gtk_menu_item_new_with_label( const char *label );
+void gtk_tree_set_selection_mode( GtkTree *tree,
+ GtkSelectionMode mode );
</verb></tscreen>
-These calls are used to create the menu items that are to be displayed.
-Remember to differentiate between a "menu" as created with gtk_menu_new
-and a "menu item" as created by the gtk_menu_item_new functions. The
-menu item will be an actual button with an associated action,
-whereas a menu will be a container holding menu items.
+The semantics associated with the various selection modes are
+described in the section on the GtkList widget. As with the GtkList
+widget, the "select_child", "unselect_child" (not really - see <ref
+id="sec_GtkTree_Signals" name="Signals"> below for an explanation),
+and "selection_changed" signals are emitted when list items are
+selected or unselected. However, in order to take advantage of these
+signals, you need to know <em>which</em> GtkTree widget they will be
+emitted by, and where to find the list of selected items.
-The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after
-reading about the buttons. One creates a new menu item with a label
-already packed into it, and the other just creates a blank menu item.
+This is a source of potential confusion. The best way to explain this
+is that though all GtkTree widgets are created equal, some are more
+equal than others. All GtkTree widgets have their own X window, and
+can therefore receive events such as mouse clicks (if their
+GtkTreeItems or their children don't catch them first!). However, to
+make GTK_SELECTION_SINGLE and GTK_SELECTION_BROWSE selection types
+behave in a sane manner, the list of selected items is specific to the
+topmost GtkTree widget in a hierarchy, known as the "root tree".
-Once you've created a menu item you have to put it into a menu. This is
-done using the function gtk_menu_append. In order to capture when the item
-is selected by the user, we need to connect to the <tt/activate/ signal in
-the usual way. So, if we wanted to create a standard <tt/File/ menu, with
-the options <tt/Open/, <tt/Save/ and <tt/Quit/ the code would look something like
+Thus, accessing the <tt>selection</tt>field directly in an arbitrary
+GtkTree widget is not a good idea unless you <em>know</em> it's the
+root tree. Instead, use the GTK_TREE_SELECTION (Tree) macro, which
+gives the root tree's selection list as a GList pointer. Of course,
+this list can include items that are not in the subtree in question if
+the selection type is GTK_SELECTION_MULTIPLE.
+
+Finally, the "select_child" (and "unselect_child", in theory) signals
+are emitted by all trees, but the "selection_changed" signal is only
+emitted by the root tree. Consequently, if you want to handle the
+"select_child" signal for a tree and all its subtrees, you will have
+to call gtk_signal_connect() for every subtree.
+
+<sect1> Tree Widget Internals
+<p>
+The GtkTree's struct definition looks like this:
<tscreen><verb>
-file_menu = gtk_menu_new(); /* Don't need to show menus */
+struct _GtkTree
+{
+ GtkContainer container;
-/* Create the menu items */
-open_item = gtk_menu_item_new_with_label("Open");
-save_item = gtk_menu_item_new_with_label("Save");
-quit_item = gtk_menu_item_new_with_label("Quit");
+ GList *children;
+
+ GtkTree* root_tree; /* owner of selection list */
+ GtkWidget* tree_owner;
+ GList *selection;
+ guint level;
+ guint indent_value;
+ guint current_indent;
+ guint selection_mode : 2;
+ guint view_mode : 1;
+ guint view_line : 1;
+};
+</verb></tscreen>
-/* Add them to the menu */
-gtk_menu_append( GTK_MENU(file_menu), open_item);
-gtk_menu_append( GTK_MENU(file_menu), save_item);
-gtk_menu_append( GTK_MENU(file_menu), quit_item);
+The perils associated with accessing the <tt>selection</tt> field
+directly have already been mentioned. The other important fields of
+the struct can also be accessed with handy macros or class functions.
+GTK_TREE_IS_ROOT_TREE (Tree) returns a boolean value which indicates
+whether a tree is the root tree in a GtkTree hierarchy, while
+GTK_TREE_ROOT_TREE (Tree) returns the root tree, an object of type
+GtkTree (so, remember to cast it using GTK_WIDGET (Tree) if you want
+to use one of the gtk_widget_*() functions on it).
-/* Attach the callback functions to the activate signal */
-gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
-gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
+Instead of directly accessing the children field of a GtkTree widget,
+it's probably best to cast it using GTK_CONTAINER (Tree), and pass it
+to the gtk_container_children() function. This creates a duplicate of
+the original list, so it's advisable to free it up using g_list_free()
+after you're done with it, or to iterate on it destructively, like
+this:
-/* We can attach the Quit menu item to our exit function */
-gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
- GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
+<tscreen><verb>
+children = gtk_container_children (GTK_CONTAINER (tree));
+while (children) {
+ do_something_nice (GTK_TREE_ITEM (children->data));
+ children = g_list_remove_link (children, children);
+}
+</verb></tscreen>
-/* We do need to show menu items */
-gtk_widget_show( open_item );
-gtk_widget_show( save_item );
-gtk_widget_show( quit_item );
+The <tt>tree_owner</tt> field is defined only in subtrees, where it
+points to the GtkTreeItem widget which holds the tree in question.
+The <tt>level</tt> field indicates how deeply nested a particular tree
+is; root trees have level 0, and each successive level of subtrees has
+a level one greater than the parent level. This field is set only
+after a GtkTree widget is actually mapped (i.e. drawn on the screen).
+
+<sect2> Signals<label id="sec_GtkTree_Signals">
+<p>
+<tscreen><verb>
+void selection_changed( GtkTree *tree );
</verb></tscreen>
-At this point we have our menu. Now we need to create a menubar and a menu
-item for the <tt/File/ entry, to which we add our menu. The code looks like this
+This signal will be emitted whenever the <tt>selection</tt> field of a
+GtkTree has changed. This happens when a child of the GtkTree is
+selected or deselected.
<tscreen><verb>
-menu_bar = gtk_menu_bar_new();
-gtk_container_add( GTK_CONTAINER(window), menu_bar);
-gtk_widget_show( menu_bar );
+void select_child( GtkTree *tree,
+ GtkWidget *child );
+</verb></tscreen>
-file_item = gtk_menu_item_new_with_label("File");
-gtk_widget_show(file_item);
+This signal is emitted when a child of the GtkTree is about to get
+selected. This happens on calls to gtk_tree_select_item(),
+gtk_tree_select_child(), on <em>all</em> button presses and calls to
+gtk_tree_item_toggle() and gtk_item_toggle(). It may sometimes be
+indirectly triggered on other occasions where children get added to or
+removed from the GtkTree.
+
+<tscreen><verb>
+void unselect_child (GtkTree *tree,
+ GtkWidget *child);
</verb></tscreen>
-Now we need to associate the menu with <tt/file_item/. This is done with the
-function
+This signal is emitted when a child of the GtkTree is about to get
+deselected. As of GTK+ 1.0.4, this seems to only occur on calls to
+gtk_tree_unselect_item() or gtk_tree_unselect_child(), and perhaps on
+other occasions, but <em>not</em> when a button press deselects a
+child, nor on emission of the "toggle" signal by gtk_item_toggle().
-<tscreen>
-void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
- GtkWidget *submenu );
-</tscreen>
+<sect2> Functions and Macros<label id="sec_GtkTree_Functions">
+<p>
+<tscreen><verb>
+guint gtk_tree_get_type( void );
+</verb></tscreen>
-So, our example would continue with
+Returns the `GtkTree' type identifier.
<tscreen><verb>
-gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );
+GtkWidget* gtk_tree_new( void );
</verb></tscreen>
-All that is left to do is to add the menu to the menubar, which is accomplished
-using the function
+Create a new GtkTree object. The new widget is returned as a pointer to a
+GtkWidget object. NULL is returned on failure.
-<tscreen>
-void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
-</tscreen>
+<tscreen><verb>
+void gtk_tree_append( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
-which in our case looks like this:
+Append a tree item to a GtkTree.
<tscreen><verb>
-gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );
+void gtk_tree_prepend( GtkTree *tree,
+ GtkWidget *tree_item );
</verb></tscreen>
-If we wanted the menu right justified on the menubar, such as help menus
-often are, we can use the following function (again on <tt/file_item/
-in the current example) before attaching it to the menubar.
+Prepend a tree item to a GtkTree.
<tscreen><verb>
-void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
+void gtk_tree_insert( GtkTree *tree,
+ GtkWidget *tree_item,
+ gint position );
</verb></tscreen>
-Here is a summary of the steps needed to create a menu bar with menus attached:
+Insert a tree item into a GtkTree at the position in the list
+specified by <tt>position.</tt>
-<itemize>
-<item> Create a new menu using gtk_menu_new()
-<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have
-on your menu. And use gtk_menu_append() to put each of these new items on
-to the menu.
-<item> Create a menu item using gtk_menu_item_new(). This will be the root of
-the menu, the text appearing here will be on the menubar itself.
-<item>Use gtk_menu_item_set_submenu() to attach the menu to the root menu
-item (the one created in the above step).
-<item> Create a new menubar using gtk_menu_bar_new. This step only needs
-to be done once when creating a series of menus on one menu bar.
-<item> Use gtk_menu_bar_append to put the root menu onto the menubar.
-</itemize>
+<tscreen><verb>
+void gtk_tree_remove_items( GtkTree *tree,
+ GList *items );
+</verb></tscreen>
-Creating a popup menu is nearly the same. The difference is that the
-menu is not posted `automatically' by a menubar, but explicitly by calling
-the function gtk_menu_popup() from a button-press event, for example.
-Take these steps:
+Remove a list of items (in the form of a GList *) from a GtkTree.
+Note that removing an item from a tree dereferences (and thus usually)
+destroys it <em>and</em> its subtree, if it has one, <em>and</em> all
+subtrees in that subtree. If you want to remove only one item, you
+can use gtk_container_remove().
-<itemize>
-<item>Create an event handling function. It needs to have the prototype
-<tscreen>
-static gint handler( GtkWidget *widget,
- GdkEvent *event );
-</tscreen>
-and it will use the event to find out where to pop up the menu.
-<item>In the event handler, if the event is a mouse button press, treat
-<tt>event</tt> as a button event (which it is) and use it as
-shown in the sample code to pass information to gtk_menu_popup().
-<item>Bind that event handler to a widget with
-<tscreen>
-gtk_signal_connect_object(GTK_OBJECT(widget), "event",
- GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
-</tscreen>
-where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt>
-is the handling function, and <tt>menu</tt> is a menu created with
-gtk_menu_new(). This can be a menu which is also posted by a menu bar,
-as shown in the sample code.
-</itemize>
+<tscreen><verb>
+void gtk_tree_clear_items( GtkTree *tree,
+ gint start,
+ gint end );
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1>Manual Menu Example
-<p>
-That should about do it. Let's take a look at an example to help clarify.
+Remove the items from position <tt>start</tt> to position <tt>end</tt>
+from a GtkTree. The same warning about dereferencing applies here, as
+gtk_tree_clear_items() simply constructs a list and passes it to
+gtk_tree_remove_items().
<tscreen><verb>
-/* example-start menu menu.c */
+void gtk_tree_select_item( GtkTree *tree,
+ gint item );
+</verb></tscreen>
-#include <gtk/gtk.h>
+Emits the "select_item" signal for the child at position
+<tt>item</tt>, thus selecting the child (unless you unselect it in a
+signal handler...)
-static gint button_press (GtkWidget *, GdkEvent *);
-static void menuitem_response (gchar *);
+<tscreen><verb>
+void gtk_tree_unselect_item( GtkTree *tree,
+ gint item );
+</verb></tscreen>
-int main (int argc, char *argv[])
-{
+Emits the "unselect_item" signal for the child at position
+<tt>item</tt>, thus unselecting the child.
- GtkWidget *window;
- GtkWidget *menu;
- GtkWidget *menu_bar;
- GtkWidget *root_menu;
- GtkWidget *menu_items;
- GtkWidget *vbox;
- GtkWidget *button;
- char buf[128];
- int i;
+<tscreen><verb>
+void gtk_tree_select_child( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
- gtk_init (&argc, &argv);
+Emits the "select_item" signal for the child <tt>tree_item</tt>, thus
+selecting it.
- /* create a new window */
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
- gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
- gtk_signal_connect(GTK_OBJECT (window), "delete_event",
- (GtkSignalFunc) gtk_main_quit, NULL);
+<tscreen><verb>
+void gtk_tree_unselect_child( GtkTree *tree,
+ GtkWidget *tree_item );
+</verb></tscreen>
- /* Init the menu-widget, and remember -- never
- * gtk_show_widget() the menu widget!!
- * This is the menu that holds the menu items, the one that
- * will pop up when you click on the "Root Menu" in the app */
- menu = gtk_menu_new();
+Emits the "unselect_item" signal for the child <tt>tree_item</tt>,
+thus unselecting it.
- /* Next we make a little loop that makes three menu-entries for "test-menu".
- * Notice the call to gtk_menu_append. Here we are adding a list of
- * menu items to our menu. Normally, we'd also catch the "clicked"
- * signal on each of the menu items and setup a callback for it,
- * but it's omitted here to save space. */
+<tscreen><verb>
+gint gtk_tree_child_position( GtkTree *tree,
+ GtkWidget *child );
+</verb></tscreen>
- for(i = 0; i < 3; i++)
- {
- /* Copy the names to the buf. */
- sprintf(buf, "Test-undermenu - %d", i);
+Returns the position in the tree of <tt>child</tt>, unless
+<tt>child</tt> is not in the tree, in which case it returns -1.
- /* Create a new menu-item with a name... */
- menu_items = gtk_menu_item_new_with_label(buf);
+<tscreen><verb>
+void gtk_tree_set_selection_mode( GtkTree *tree,
+ GtkSelectionMode mode );
+</verb></tscreen>
- /* ...and add it to the menu. */
- gtk_menu_append(GTK_MENU (menu), menu_items);
+Sets the selection mode, which can be one of GTK_SELECTION_SINGLE (the
+default), GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE, or
+GTK_SELECTION_EXTENDED. This is only defined for root trees, which
+makes sense, since the root tree "owns" the selection. Setting it for
+subtrees has no effect at all; the value is simply ignored.
- /* Do something interesting when the menuitem is selected */
- gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
- GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
+<tscreen><verb>
+void gtk_tree_set_view_mode( GtkTree *tree,
+ GtkTreeViewMode mode );
+</verb></tscreen>
- /* Show the widget */
- gtk_widget_show(menu_items);
- }
+Sets the "view mode", which can be either GTK_TREE_VIEW_LINE (the
+default) or GTK_TREE_VIEW_ITEM. The view mode propagates from a tree
+to its subtrees, and can't be set exclusively to a subtree (this is
+not exactly true - see the example code comments).
- /* This is the root menu, and will be the label
- * displayed on the menu bar. There won't be a signal handler attached,
- * as it only pops up the rest of the menu when pressed. */
- root_menu = gtk_menu_item_new_with_label("Root Menu");
+The term "view mode" is rather ambiguous - basically, it controls the
+way the hilight is drawn when one of a tree's children is selected.
+If it's GTK_TREE_VIEW_LINE, the entire GtkTreeItem widget is
+hilighted, while for GTK_TREE_VIEW_ITEM, only the child widget
+(i.e. usually the label) is hilighted.
- gtk_widget_show(root_menu);
+<tscreen><verb>
+void gtk_tree_set_view_lines( GtkTree *tree,
+ guint flag );
+</verb></tscreen>
- /* Now we specify that we want our newly created "menu" to be the menu
- * for the "root menu" */
- gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
+Controls whether connecting lines between tree items are drawn.
+<tt>flag</tt> is either TRUE, in which case they are, or FALSE, in
+which case they aren't.
- /* A vbox to put a menu and a button in: */
- vbox = gtk_vbox_new(FALSE, 0);
- gtk_container_add(GTK_CONTAINER(window), vbox);
- gtk_widget_show(vbox);
+<tscreen><verb>
+GtkTree *GTK_TREE (gpointer obj);
+</verb></tscreen>
- /* Create a menu-bar to hold the menus and add it to our main window */
- menu_bar = gtk_menu_bar_new();
- gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
- gtk_widget_show(menu_bar);
+Cast a generic pointer to `GtkTree *'.
- /* Create a button to which to attach menu as a popup */
- button = gtk_button_new_with_label("press me");
- gtk_signal_connect_object(GTK_OBJECT(button), "event",
- GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
- gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
- gtk_widget_show(button);
+<tscreen><verb>
+GtkTreeClass *GTK_TREE_CLASS (gpointer class);
+</verb></tscreen>
- /* And finally we append the menu-item to the menu-bar -- this is the
- * "root" menu-item I have been raving about =) */
- gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
+Cast a generic pointer to `GtkTreeClass*'.
- /* always display the window as the last step so it all splashes on
- * the screen at once. */
- gtk_widget_show(window);
+<tscreen><verb>
+gint GTK_IS_TREE (gpointer obj);
+</verb></tscreen>
- gtk_main ();
+Determine if a generic pointer refers to a `GtkTree' object.
- return 0;
-}
+<tscreen><verb>
+gint GTK_IS_ROOT_TREE (gpointer obj)
+</verb></tscreen>
-/* Respond to a button-press by posting a menu passed in as widget.
- *
- * Note that the "widget" argument is the menu being posted, NOT
- * the button that was pressed.
- */
+Determine if a generic pointer refers to a `GtkTree' object
+<em>and</em> is a root tree. Though this will accept any pointer, the
+results of passing it a pointer that does not refer to a GtkTree are
+undefined and possibly harmful.
-static gint button_press (GtkWidget *widget, GdkEvent *event)
-{
+<tscreen><verb>
+GtkTree *GTK_TREE_ROOT_TREE (gpointer obj)
+</verb></tscreen>
- if (event->type == GDK_BUTTON_PRESS) {
- GdkEventButton *bevent = (GdkEventButton *) event;
- gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
- bevent->button, bevent->time);
- /* Tell calling code that we have handled this event; the buck
- * stops here. */
- return TRUE;
- }
+Return the root tree of a pointer to a `GtkTree' object. The above
+warning applies.
- /* Tell calling code that we have not handled this event; pass it on. */
- return FALSE;
-}
+<tscreen><verb>
+GList *GTK_TREE_SELECTION( gpointer obj)
+</verb></tscreen>
+Return the selection list of the root tree of a `GtkTree' object. The
+above warning applies here, too.
-/* Print a string when a menu item is selected */
+<sect1> Tree Item Widget<label id="sec_Tree_Item_Widget">
+<p>
+The GtkTreeItem widget, like GtkListItem, is derived from GtkItem,
+which in turn is derived from GtkBin. Therefore, the item itself is a
+generic container holding exactly one child widget, which can be of
+any type. The GtkTreeItem widget has a number of extra fields, but
+the only one we need be concerned with is the <tt>subtree</tt> field.
-static void menuitem_response (gchar *string)
+The definition for the GtkTreeItem struct looks like this:
+
+<tscreen><verb>
+struct _GtkTreeItem
{
- printf("%s\n", string);
+ GtkItem item;
+
+ GtkWidget *subtree;
+ GtkWidget *pixmaps_box;
+ GtkWidget *plus_pix_widget, *minus_pix_widget;
+
+ GList *pixmaps; /* pixmap node for this items color depth */
+
+ guint expanded : 1;
+};
+</verb></tscreen>
+
+The <tt>pixmaps_box</tt> field is a GtkEventBox which catches clicks
+on the plus/minus symbol which controls expansion and collapsing. The
+<tt>pixmaps</tt> field points to an internal data structure. Since
+you can always obtain the subtree of a GtkTreeItem in a (relatively)
+type-safe manner with the GTK_TREE_ITEM_SUBTREE (Item) macro, it's
+probably advisable never to touch the insides of a GtkTreeItem unless
+you <em>really</em> know what you're doing.
+
+Since it is directly derived from a GtkItem it can be treated as such
+by using the GTK_ITEM (TreeItem) macro. A GtkTreeItem usually holds a
+label, so the convenience function gtk_list_item_new_with_label() is
+provided. The same effect can be achieved using code like the
+following, which is actually copied verbatim from
+gtk_tree_item_new_with_label():
+
+<tscreen><verb>
+tree_item = gtk_tree_item_new ();
+label_widget = gtk_label_new (label);
+gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5);
+
+gtk_container_add (GTK_CONTAINER (tree_item), label_widget);
+gtk_widget_show (label_widget);
+</verb></tscreen>
+
+As one is not forced to add a GtkLabel to a GtkTreeItem, you could
+also add a GtkHBox or a GtkArrow, or even a GtkNotebook (though your
+app will likely be quite unpopular in this case) to the GtkTreeItem.
+
+If you remove all the items from a subtree, it will be destroyed and
+unparented, unless you reference it beforehand, and the GtkTreeItem
+which owns it will be collapsed. So, if you want it to stick around,
+do something like the following:
+
+<tscreen><verb>
+gtk_widget_ref (tree);
+owner = GTK_TREE(tree)->tree_owner;
+gtk_container_remove (GTK_CONTAINER(tree), item);
+if (tree->parent == NULL){
+ gtk_tree_item_expand (GTK_TREE_ITEM(owner));
+ gtk_tree_item_set_subtree (GTK_TREE_ITEM(owner), tree);
}
-/* example-end */
+else
+ gtk_widget_unref (tree);
</verb></tscreen>
-You may also set a menu item to be insensitive and, using an accelerator
-table, bind keys to menu functions.
+Finally, drag-n-drop <em>does</em> work with GtkTreeItems. You just
+have to make sure that the GtkTreeItem you want to make into a drag
+item or a drop site has not only been added to a GtkTree, but that
+each successive parent widget has a parent itself, all the way back to
+a toplevel or dialog window, when you call gtk_widget_dnd_drag_set()
+or gtk_widget_dnd_drop_set(). Otherwise, strange things will happen.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Using GtkMenuFactory
+<sect2> Signals
<p>
-Now that we've shown you the hard way, here's how you do it using the
-gtk_menu_factory calls.
+GtkTreeItem inherits the "select", "deselect", and "toggle" signals
+from GtkItem. In addition, it adds two signals of its own, "expand"
+and "collapse".
-<!-- ----------------------------------------------------------------- -->
-<sect1>Menu Factory Example
+<tscreen><verb>
+void select( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when an item is about to be selected, either
+after it has been clicked on by the user, or when the program calls
+gtk_tree_item_select(), gtk_item_select(), or gtk_tree_select_child().
+
+<tscreen><verb>
+void deselect( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when an item is about to be unselected, either
+after it has been clicked on by the user, or when the program calls
+gtk_tree_item_deselect() or gtk_item_deselect(). In the case of
+GtkTreeItems, it is also emitted by gtk_tree_unselect_child(), and
+sometimes gtk_tree_select_child().
+
+<tscreen><verb>
+void toggle( GtkItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the program calls gtk_item_toggle(). The
+effect it has when emitted on a GtkTreeItem is to call
+gtk_tree_select_child() (and never gtk_tree_unselect_child()) on the
+item's parent tree, if the item has a parent tree. If it doesn't,
+then the highlight is reversed on the item.
+
+<tscreen><verb>
+void expand( GtkTreeItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the tree item's subtree is about to be
+expanded, that is, when the user clicks on the plus sign next to the
+item, or when the program calls gtk_tree_item_expand().
+
+<tscreen><verb>
+void collapse( GtkTreeItem *tree_item );
+</verb></tscreen>
+
+This signal is emitted when the tree item's subtree is about to be
+collapsed, that is, when the user clicks on the minus sign next to the
+item, or when the program calls gtk_tree_item_collapse().
+
+<sect2> Functions and Macros
<p>
-Here is an example using the GTK menu factory. This is the first file,
-menufactory.h. We keep a separate menufactory.c and mfmain.c because
-of the global variables used in the menufactory.c file.
+<tscreen><verb>
+guint gtk_tree_item_get_type( void );
+</verb></tscreen>
+
+Returns the `GtkTreeItem' type identifier.
<tscreen><verb>
-/* example-start menu menufactory.h */
+GtkWidget* gtk_tree_item_new( void );
+</verb></tscreen>
-#ifndef __MENUFACTORY_H__
-#define __MENUFACTORY_H__
+Create a new GtkTreeItem object. The new widget is returned as a pointer
+to a GtkWidget object. NULL is returned on failure.
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+<tscreen><verb>
+GtkWidget* gtk_tree_item_new_with_label (gchar *label);
+</verb></tscreen>
-void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
-void menus_create(GtkMenuEntry *entries, int nmenu_entries);
+Create a new GtkTreeItem object, having a single GtkLabel as
+the sole child. The new widget is returned as a pointer to a
+GtkWidget object. NULL is returned on failure.
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+<tscreen><verb>
+void gtk_tree_item_select( GtkTreeItem *tree_item );
+</verb></tscreen>
-#endif /* __MENUFACTORY_H__ */
-/* example-end */
+This function is basicaly a wrapper around a call to
+gtk_item_select (GTK_ITEM (tree_item)) which will emit the
+select signal.
+
+<tscreen><verb>
+void gtk_tree_item_deselect( GtkTreeItem *tree_item );
</verb></tscreen>
-And here is the menufactory.c file.
+This function is basicaly a wrapper around a call to
+gtk_item_deselect (GTK_ITEM (tree_item)) which will emit the
+deselect signal.
<tscreen><verb>
-/* example-start menu menufactory.c */
+void gtk_tree_item_set_subtree( GtkTreeItem *tree_item,
+ GtkWidget *subtree );
+</verb></tscreen>
-#include <gtk/gtk.h>
-#include <strings.h>
+This function adds subtree to tree_item, showing it if tree_item is
+expanded, or hiding it if tree_item is collapsed. Again, remember
+that the tree_item must have already been added to a tree for this to
+work.
-#include "mfmain.h"
+<tscreen><verb>
+void gtk_tree_item_remove_subtree( GtkTreeItem *tree_item );
+</verb></tscreen>
+This removes all of tree_item's subtree's children (thus unreferencing
+and destroying it, any of its children's subtrees, and so on...), then
+removes the subtree itself, and hides the plus/minus sign.
-static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
-static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
-void menus_init(void);
-void menus_create(GtkMenuEntry * entries, int nmenu_entries);
+<tscreen><verb>
+void gtk_tree_item_expand( GtkTreeItem *tree_item );
+</verb></tscreen>
+This emits the "expand" signal on tree_item, which expands it.
-/* this is the GtkMenuEntry structure used to create new menus. The
- * first member is the menu definition string. The second, the
- * default accelerator key used to access this menu function with
- * the keyboard. The third is the callback function to call when
- * this menu item is selected (by the accelerator key, or with the
- * mouse.) The last member is the data to pass to your callback function.
- */
+<tscreen><verb>
+void gtk_tree_item_collapse( GtkTreeItem *tree_item );
+</verb></tscreen>
-static GtkMenuEntry menu_items[] =
-{
- {"<Main>/File/New", "<control>N", NULL, NULL},
- {"<Main>/File/Open", "<control>O", NULL, NULL},
- {"<Main>/File/Save", "<control>S", NULL, NULL},
- {"<Main>/File/Save as", NULL, NULL, NULL},
- {"<Main>/File/<separator>", NULL, NULL, NULL},
- {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
- {"<Main>/Options/Test", NULL, NULL, NULL}
-};
+This emits the "collapse" signal on tree_item, which collapses it.
-/* calculate the number of menu_item's */
-static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
+<tscreen><verb>
+GtkTreeItem *GTK_TREE_ITEM (gpointer obj)
+</verb></tscreen>
-static int initialize = TRUE;
-static GtkMenuFactory *factory = NULL;
-static GtkMenuFactory *subfactory[1];
-static GHashTable *entry_ht = NULL;
+Cast a generic pointer to `GtkTreeItem*'.
-void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
+<tscreen><verb>
+GtkTreeItemClass *GTK_TREE_ITEM_CLASS (gpointer obj)
+</verb></tscreen>
+
+Cast a generic pointer to `GtkTreeItemClass'.
+
+<tscreen><verb>
+gint GTK_IS_TREE_ITEM (gpointer obj)
+</verb></tscreen>
+
+Determine if a generic pointer refers to a `GtkTreeItem' object.
+
+<tscreen><verb>
+GtkWidget GTK_TREE_ITEM_SUBTREE (gpointer obj)
+</verb></tscreen>
+
+Return's a tree item's subtree (obj should point to a `GtkTreeItem'
+object).
+
+<sect1> Tree Example
+<p>
+This is somewhat like the tree example in testgtk.c, but a lot less
+complete (although much better commented). It puts up a window with a
+tree, and connects all the signals for the relevant objects, so you
+can see when they are emitted.
+
+<tscreen><verb>
+/* example-start tree tree.c */
+
+#include <gtk/gtk.h>
+
+/* for all the GtkItem:: and GtkTreeItem:: signals */
+static void cb_itemsignal (GtkWidget *item, gchar *signame)
{
- if (initialize)
- menus_init();
-
- if (menubar)
- *menubar = subfactory[0]->widget;
- if (table)
- *table = subfactory[0]->table;
+ gchar *name;
+ GtkLabel *label;
+
+ /* It's a GtkBin, so it has one child, which we know to be a
+ label, so get that */
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ /* Get the text of the label */
+ gtk_label_get (label, &name);
+ /* Get the level of the tree which the item is in */
+ g_print ("%s called for item %s->%p, level %d\n", signame, name,
+ item, GTK_TREE (item->parent)->level);
}
-void menus_init(void)
+/* Note that this is never called */
+static void cb_unselect_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
{
- if (initialize) {
- initialize = FALSE;
-
- factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
- subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
-
- gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
- menus_create(menu_items, nmenu_items);
- }
+ g_print ("unselect_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
}
-void menus_create(GtkMenuEntry * entries, int nmenu_entries)
+/* Note that this is called every time the user clicks on an item,
+ whether it is already selected or not. */
+static void cb_select_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
{
- char *accelerator;
- int i;
-
- if (initialize)
- menus_init();
-
- if (entry_ht)
- for (i = 0; i < nmenu_entries; i++) {
- accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
- if (accelerator) {
- if (accelerator[0] == '\0')
- entries[i].accelerator = NULL;
- else
- entries[i].accelerator = accelerator;
- }
- }
- gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
-
- for (i = 0; i < nmenu_entries; i++)
- if (entries[i].widget) {
- gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
- (GtkSignalFunc) menus_install_accel,
- entries[i].path);
- gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
- (GtkSignalFunc) menus_remove_accel,
- entries[i].path);
- }
+ g_print ("select_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
}
-static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
+static void cb_selection_changed (GtkWidget *tree)
{
- char accel[64];
- char *t1, t2[2];
-
- accel[0] = '\0';
- if (modifiers & GDK_CONTROL_MASK)
- strcat(accel, "<control>");
- if (modifiers & GDK_SHIFT_MASK)
- strcat(accel, "<shift>");
- if (modifiers & GDK_MOD1_MASK)
- strcat(accel, "<alt>");
-
- t2[0] = key;
- t2[1] = '\0';
- strcat(accel, t2);
-
- if (entry_ht) {
- t1 = g_hash_table_lookup(entry_ht, path);
- g_free(t1);
- } else
- entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
-
- g_hash_table_insert(entry_ht, path, g_strdup(accel));
-
- return TRUE;
+ GList *i;
+
+ g_print ("selection_change called for tree %p\n", tree);
+ g_print ("selected objects are:\n");
+
+ i = GTK_TREE_SELECTION(tree);
+ while (i){
+ gchar *name;
+ GtkLabel *label;
+ GtkWidget *item;
+
+ /* Get a GtkWidget pointer from the list node */
+ item = GTK_WIDGET (i->data);
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ gtk_label_get (label, &name);
+ g_print ("\t%s on level %d\n", name, GTK_TREE
+ (item->parent)->level);
+ i = i->next;
+ }
}
-static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
+int main (int argc, char *argv[])
{
- char *t;
-
- if (entry_ht) {
- t = g_hash_table_lookup(entry_ht, path);
- g_free(t);
-
- g_hash_table_insert(entry_ht, path, g_strdup(""));
+ GtkWidget *window, *scrolled_win, *tree;
+ static gchar *itemnames[] = {"Foo", "Bar", "Baz", "Quux",
+ "Maurice"};
+ gint i;
+
+ gtk_init (&argc, &argv);
+
+ /* a generic toplevel window */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect (GTK_OBJECT(window), "delete_event",
+ GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
+ gtk_container_border_width (GTK_CONTAINER(window), 5);
+
+ /* A generic scrolled window */
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_usize (scrolled_win, 150, 200);
+ gtk_container_add (GTK_CONTAINER(window), scrolled_win);
+ gtk_widget_show (scrolled_win);
+
+ /* Create the root tree */
+ tree = gtk_tree_new();
+ g_print ("root tree is %p\n", tree);
+ /* connect all GtkTree:: signals */
+ gtk_signal_connect (GTK_OBJECT(tree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "selection_changed",
+ GTK_SIGNAL_FUNC(cb_selection_changed), tree);
+ /* Add it to the scrolled window */
+ gtk_container_add (GTK_CONTAINER(scrolled_win), tree);
+ /* Set the selection mode */
+ gtk_tree_set_selection_mode (GTK_TREE(tree),
+ GTK_SELECTION_MULTIPLE);
+ /* Show it */
+ gtk_widget_show (tree);
+
+ for (i = 0; i < 5; i++){
+ GtkWidget *subtree, *item;
+ gint j;
+
+ /* Create a tree item */
+ item = gtk_tree_item_new_with_label (itemnames[i]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(item), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(item), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(item), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(item), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(item), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ /* Add it to the parent tree */
+ gtk_tree_append (GTK_TREE(tree), item);
+ /* Show it - this can be done at any time */
+ gtk_widget_show (item);
+ /* Create this item's subtree */
+ subtree = gtk_tree_new();
+ g_print ("-> item %s->%p, subtree %p\n", itemnames[i], item,
+ subtree);
+
+ /* This is still necesary if you want these signals to be called
+ for the subtree's children. Note that selection_change will be
+ signalled for the root tree regardless. */
+ gtk_signal_connect (GTK_OBJECT(subtree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), subtree);
+ gtk_signal_connect (GTK_OBJECT(subtree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), subtree);
+ /* This has absolutely no effect, because it is completely ignored
+ in subtrees */
+ gtk_tree_set_selection_mode (GTK_TREE(subtree),
+ GTK_SELECTION_SINGLE);
+ /* Neither does this, but for a rather different reason - the
+ view_mode and view_line values of a tree are propagated to
+ subtrees when they are mapped. So, setting it later on would
+ actually have a (somewhat unpredictable) effect */
+ gtk_tree_set_view_mode (GTK_TREE(subtree), GTK_TREE_VIEW_ITEM);
+ /* Set this item's subtree - note that you cannot do this until
+ AFTER the item has been added to its parent tree! */
+ gtk_tree_item_set_subtree (GTK_TREE_ITEM(item), subtree);
+
+ for (j = 0; j < 5; j++){
+ GtkWidget *subitem;
+
+ /* Create a subtree item, in much the same way */
+ subitem = gtk_tree_item_new_with_label (itemnames[j]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(subitem), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(subitem), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(subitem), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(subitem), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(subitem), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ g_print ("-> -> item %s->%p\n", itemnames[j], subitem);
+ /* Add it to its parent tree */
+ gtk_tree_append (GTK_TREE(subtree), subitem);
+ /* Show it */
+ gtk_widget_show (subitem);
}
-}
+ }
-void menus_set_sensitive(char *path, int sensitive)
-{
- GtkMenuPath *menu_path;
-
- if (initialize)
- menus_init();
-
- menu_path = gtk_menu_factory_find(factory, path);
- if (menu_path)
- gtk_widget_set_sensitive(menu_path->widget, sensitive);
- else
- g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path);
+ /* Show the window and loop endlessly */
+ gtk_widget_show (window);
+ gtk_main();
+ return 0;
}
/* example-end */
</verb></tscreen>
-And here's the mfmain.h
-
-<tscreen><verb>
-/* example-start menu mfmain.h */
+<!-- ***************************************************************** -->
+<sect>Menu Widget
+<!-- ***************************************************************** -->
+<p>
+There are two ways to create menus, there's the easy way, and there's the
+hard way. Both have their uses, but you can usually use the menufactory
+(the easy way). The "hard" way is to create all the menus using the calls
+directly. The easy way is to use the gtk_menu_factory calls. This is
+much simpler, but there are advantages and disadvantages to each approach.
-#ifndef __MFMAIN_H__
-#define __MFMAIN_H__
+The menufactory is much easier to use, and to add new menus to, although
+writing a few wrapper functions to create menus using the manual method
+could go a long way towards usability. With the menufactory, it is not
+possible to add images or the character '/' to the menus.
+<!-- ----------------------------------------------------------------- -->
+<sect1>Manual Menu Creation
+<p>
+In the true tradition of teaching, we'll show you the hard
+way first. <tt>:)</>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+There are three widgets that go into making a menubar and submenus:
+<itemize>
+<item>a menu item, which is what the user wants to select, e.g. 'Save'
+<item>a menu, which acts as a container for the menu items, and
+<item>a menubar, which is a container for each of the individual menus,
+</itemize>
-void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
+This is slightly complicated by the fact that menu item widgets are used
+for two different things. They are both the widets that are packed into
+the menu, and the widget that is packed into the menubar, which,
+when selected, activiates the menu.
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+Let's look at the functions that are used to create menus and menubars.
+This first function is used to create a new menubar.
-#endif /* __MFMAIN_H__ */
-/* example-end */
+<tscreen><verb>
+GtkWidget *gtk_menu_bar_new( void );
</verb></tscreen>
-And mfmain.c
+This rather self explanatory function creates a new menubar. You use
+gtk_container_add to pack this into a window, or the box_pack functions to
+pack it into a box - the same as buttons.
<tscreen><verb>
-/* example-start menu mfmain.c */
-
-#include <gtk/gtk.h>
-
-#include "mfmain.h"
-#include "menufactory.h"
+GtkWidget *gtk_menu_new( void );
+</verb></tscreen>
+This function returns a pointer to a new menu, it is never actually shown
+(with gtk_widget_show), it is just a container for the menu items. Hopefully this will
+become more clear when you look at the example below.
-int main(int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *main_vbox;
- GtkWidget *menubar;
-
- GtkAcceleratorTable *accel;
-
- gtk_init(&argc, &argv);
-
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_signal_connect(GTK_OBJECT(window), "destroy",
- GTK_SIGNAL_FUNC(file_quit_cmd_callback),
- "WM destroy");
- gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
- gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
-
- main_vbox = gtk_vbox_new(FALSE, 1);
- gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
- gtk_container_add(GTK_CONTAINER(window), main_vbox);
- gtk_widget_show(main_vbox);
-
- get_main_menu(&menubar, &accel);
- gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
- gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
- gtk_widget_show(menubar);
-
- gtk_widget_show(window);
- gtk_main();
-
- return(0);
-}
+The next two calls are used to create menu items that are packed into
+the menu (and menubar).
-/* This is just to demonstrate how callbacks work when using the
- * menufactory. Often, people put all the callbacks from the menus
- * in a separate file, and then have them call the appropriate functions
- * from there. Keeps it more organized. */
-void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
-{
- g_print ("%s\n", (char *) data);
- gtk_exit(0);
-}
-/* example-end */
+<tscreen><verb>
+GtkWidget *gtk_menu_item_new( void );
</verb></tscreen>
-And a makefile so it'll be easier to compile it.
+and
<tscreen><verb>
-# Makefile.mf
+GtkWidget *gtk_menu_item_new_with_label( const char *label );
+</verb></tscreen>
-CC = gcc
-PROF = -g
-C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
-L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
-L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
-PROGNAME = menufactory
+These calls are used to create the menu items that are to be displayed.
+Remember to differentiate between a "menu" as created with gtk_menu_new
+and a "menu item" as created by the gtk_menu_item_new functions. The
+menu item will be an actual button with an associated action,
+whereas a menu will be a container holding menu items.
-O_FILES = menufactory.o mfmain.o
+The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after
+reading about the buttons. One creates a new menu item with a label
+already packed into it, and the other just creates a blank menu item.
-$(PROGNAME): $(O_FILES)
- rm -f $(PROGNAME)
- $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
+Once you've created a menu item you have to put it into a menu. This is
+done using the function gtk_menu_append. In order to capture when the item
+is selected by the user, we need to connect to the <tt/activate/ signal in
+the usual way. So, if we wanted to create a standard <tt/File/ menu, with
+the options <tt/Open/, <tt/Save/ and <tt/Quit/ the code would look something like
-.c.o:
- $(CC) -c $(C_FLAGS) $<
+<tscreen><verb>
+file_menu = gtk_menu_new(); /* Don't need to show menus */
-clean:
- rm -f core *.o $(PROGNAME) nohup.out
-distclean: clean
- rm -f *~
-</verb></tscreen>
+/* Create the menu items */
+open_item = gtk_menu_item_new_with_label("Open");
+save_item = gtk_menu_item_new_with_label("Save");
+quit_item = gtk_menu_item_new_with_label("Quit");
-For now, there's only this example. An explanation and lots 'o' comments
-will follow later.
+/* Add them to the menu */
+gtk_menu_append( GTK_MENU(file_menu), open_item);
+gtk_menu_append( GTK_MENU(file_menu), save_item);
+gtk_menu_append( GTK_MENU(file_menu), quit_item);
-<!-- ***************************************************************** -->
-<sect> Text Widget
-<!-- ***************************************************************** -->
-<p>
-The Text widget allows multiple lines of text to be displayed and edited.
-It supports both multi-colored and multi-font text, allowing them to be
-mixed in any way we wish. It also has a wide set of key based text editing
-commands, which are compatible with Emacs.
+/* Attach the callback functions to the activate signal */
+gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
+gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
-The text widget supports full cut-and-paste facilities, including the use
-of double- and triple-click to select a word and a whole line, respectively.
+/* We can attach the Quit menu item to our exit function */
+gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
+ GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
-<!-- ----------------------------------------------------------------- -->
-<sect1>Creating and Configuring a Text box
-<p>
-There is only one function for creating a new Text widget.
-<tscreen><verb>
-GtkWidget *gtk_text_new( GtkAdjustment *hadj,
- GtkAdjustment *vadj );
+/* We do need to show menu items */
+gtk_widget_show( open_item );
+gtk_widget_show( save_item );
+gtk_widget_show( quit_item );
</verb></tscreen>
-The arguments allow us to give the Text widget pointers to Adjustments
-that can be used to track the viewing position of the widget. Passing NULL
-values to either or both of these arguments will cause the gtk_text_new
-function to create it's own.
+At this point we have our menu. Now we need to create a menubar and a menu
+item for the <tt/File/ entry, to which we add our menu. The code looks like this
<tscreen><verb>
-void gtk_text_set_adjustments( GtkText *text,
- GtkAdjustment *hadj,
- GtkAdjustment *vadj );
+menu_bar = gtk_menu_bar_new();
+gtk_container_add( GTK_CONTAINER(window), menu_bar);
+gtk_widget_show( menu_bar );
+
+file_item = gtk_menu_item_new_with_label("File");
+gtk_widget_show(file_item);
</verb></tscreen>
-The above function allows the horizontal and vertical adjustments of a
-Text widget to be changed at any time.
+Now we need to associate the menu with <tt/file_item/. This is done with the
+function
-The text widget will not automatically create it's own scrollbars when
-the amount of text to be displayed is too long for the display window. We
-therefore have to create and add them to the display layout ourselves.
+<tscreen>
+void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
+ GtkWidget *submenu );
+</tscreen>
+
+So, our example would continue with
<tscreen><verb>
- vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
- gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
- gtk_widget_show (vscrollbar);
+gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );
</verb></tscreen>
-The above code snippet creates a new vertical scrollbar, and attaches
-it to the vertical adjustment of the text widget, <tt/text/. It then packs
-it into a box in the normal way.
+All that is left to do is to add the menu to the menubar, which is accomplished
+using the function
-Note, currently the GtkText widget does not support horizontal scrollbars.
+<tscreen>
+void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
+</tscreen>
-There are two main ways in which a Text widget can be used: to allow the
-user to edit a body of text, or to allow us to display multiple lines of
-text to the user. In order for us to switch between these modes of
-operation, the text widget has the following function:
+which in our case looks like this:
<tscreen><verb>
-void gtk_text_set_editable( GtkText *text,
- gint editable );
+gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );
</verb></tscreen>
-The <tt/editable/ argument is a TRUE or FALSE value that specifies whether
-the user is permitted to edit the contents of the Text widget. When the
-text widget is editable, it will display a cursor at the current insertion
-point.
-
-You are not, however, restricted to just using the text widget in these
-two modes. You can toggle the editable state of the text widget at any
-time, and can insert text at any time.
-
-The text widget wraps lines of text that are too long to
-fit onto a single line of the display window. It's default behaviour is
-to break words across line breaks. This can be changed using the next
-function:
+If we wanted the menu right justified on the menubar, such as help menus
+often are, we can use the following function (again on <tt/file_item/
+in the current example) before attaching it to the menubar.
<tscreen><verb>
-void gtk_text_set_word_wrap( GtkText *text,
- gint word_wrap );
+void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
</verb></tscreen>
-Using this function allows us to specify that the text widget should
-wrap long lines on word boundaries. The <tt/word_wrap/ argument is a
-TRUE or FALSE value.
+Here is a summary of the steps needed to create a menu bar with menus attached:
-<!-- ----------------------------------------------------------------- -->
-<sect1>Text Manipulation
-<P>
-The current insertion point of a Text widget can be set using
-<tscreen><verb>
-void gtk_text_set_point( GtkText *text,
- guint index );
-</verb></tscreen>
+<itemize>
+<item> Create a new menu using gtk_menu_new()
+<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have
+on your menu. And use gtk_menu_append() to put each of these new items on
+to the menu.
+<item> Create a menu item using gtk_menu_item_new(). This will be the root of
+the menu, the text appearing here will be on the menubar itself.
+<item>Use gtk_menu_item_set_submenu() to attach the menu to the root menu
+item (the one created in the above step).
+<item> Create a new menubar using gtk_menu_bar_new. This step only needs
+to be done once when creating a series of menus on one menu bar.
+<item> Use gtk_menu_bar_append to put the root menu onto the menubar.
+</itemize>
-where <tt/index/ is the position to set the insertion point.
+Creating a popup menu is nearly the same. The difference is that the
+menu is not posted `automatically' by a menubar, but explicitly by calling
+the function gtk_menu_popup() from a button-press event, for example.
+Take these steps:
-Analogous to this is the function for getting the current insertion point:
+<itemize>
+<item>Create an event handling function. It needs to have the prototype
+<tscreen>
+static gint handler( GtkWidget *widget,
+ GdkEvent *event );
+</tscreen>
+and it will use the event to find out where to pop up the menu.
+<item>In the event handler, if the event is a mouse button press, treat
+<tt>event</tt> as a button event (which it is) and use it as
+shown in the sample code to pass information to gtk_menu_popup().
+<item>Bind that event handler to a widget with
+<tscreen>
+gtk_signal_connect_object(GTK_OBJECT(widget), "event",
+ GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
+</tscreen>
+where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt>
+is the handling function, and <tt>menu</tt> is a menu created with
+gtk_menu_new(). This can be a menu which is also posted by a menu bar,
+as shown in the sample code.
+</itemize>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Manual Menu Example
+<p>
+That should about do it. Let's take a look at an example to help clarify.
<tscreen><verb>
-guint gtk_text_get_point( GtkText *text );
-</verb></tscreen>
+/* example-start menu menu.c */
-A function that is useful in combination with the above two functions is
+#include <gtk/gtk.h>
-<tscreen><verb>
-guint gtk_text_get_length( GtkText *text );
-</verb></tscreen>
+static gint button_press (GtkWidget *, GdkEvent *);
+static void menuitem_response (gchar *);
-which returns the current length of the Text widget. The length is the
-number of characters that are within the text block of the widget,
-including characters such as carriage-return, which marks the end of lines.
+int main (int argc, char *argv[])
+{
-In order to insert text at the current insertion point of a Text
-widget, the function gtk_text_insert is used, which also allows us to
-specify background and foreground colors and a font for the text.
+ GtkWidget *window;
+ GtkWidget *menu;
+ GtkWidget *menu_bar;
+ GtkWidget *root_menu;
+ GtkWidget *menu_items;
+ GtkWidget *vbox;
+ GtkWidget *button;
+ char buf[128];
+ int i;
-<tscreen><verb>
-void gtk_text_insert( GtkText *text,
- GdkFont *font,
- GdkColor *fore,
- GdkColor *back,
- const char *chars,
- gint length );
-</verb></tscreen>
+ gtk_init (&argc, &argv);
-Passing a value of <tt/NULL/ in as the value for the foreground color,
-background colour or font will result in the values set within the widget
-style to be used. Using a value of <tt/-1/ for the length parameter will
-result in the whole of the text string given being inserted.
+ /* create a new window */
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
+ gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
+ gtk_signal_connect(GTK_OBJECT (window), "delete_event",
+ (GtkSignalFunc) gtk_main_quit, NULL);
-The text widget is one of the few within GTK that redraws itself
-dynamically, outside of the gtk_main function. This means that all changes
-to the contents of the text widget take effect immediately. This may be
-undesirable when performing multiple changes to the text widget. In order
-to allow us to perform multiple updates to the text widget without it
-continuously redrawing, we can freeze the widget, which temporarily stops
-it from automatically redrawing itself every time it is changed. We can
-then thaw the widget after our updates are complete.
+ /* Init the menu-widget, and remember -- never
+ * gtk_show_widget() the menu widget!!
+ * This is the menu that holds the menu items, the one that
+ * will pop up when you click on the "Root Menu" in the app */
+ menu = gtk_menu_new();
-The following two functions perform this freeze and thaw action:
+ /* Next we make a little loop that makes three menu-entries for "test-menu".
+ * Notice the call to gtk_menu_append. Here we are adding a list of
+ * menu items to our menu. Normally, we'd also catch the "clicked"
+ * signal on each of the menu items and setup a callback for it,
+ * but it's omitted here to save space. */
-<tscreen><verb>
-void gtk_text_freeze( GtkText *text );
+ for(i = 0; i < 3; i++)
+ {
+ /* Copy the names to the buf. */
+ sprintf(buf, "Test-undermenu - %d", i);
-void gtk_text_thaw( GtkText *text );
-</verb></tscreen>
+ /* Create a new menu-item with a name... */
+ menu_items = gtk_menu_item_new_with_label(buf);
-Text is deleted from the text widget relative to the current insertion
-point by the following two functions. The return value is a TRUE or
-FALSE indicator of whether the operation was successful.
+ /* ...and add it to the menu. */
+ gtk_menu_append(GTK_MENU (menu), menu_items);
-<tscreen><verb>
-gint gtk_text_backward_delete( GtkText *text,
- guint nchars );
+ /* Do something interesting when the menuitem is selected */
+ gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
+ GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
-gint gtk_text_forward_delete ( GtkText *text,
- guint nchars );
-</verb></tscreen>
+ /* Show the widget */
+ gtk_widget_show(menu_items);
+ }
-If you want to retrieve the contents of the text widget, then the macro
-<tt/GTK_TEXT_INDEX(t, index)/ allows you to retrieve the character at
-position <tt/index/ within the text widget <tt/t/.
+ /* This is the root menu, and will be the label
+ * displayed on the menu bar. There won't be a signal handler attached,
+ * as it only pops up the rest of the menu when pressed. */
+ root_menu = gtk_menu_item_new_with_label("Root Menu");
-To retrieve larger blocks of text, we can use the function
+ gtk_widget_show(root_menu);
-<tscreen><verb>
-gchar *gtk_editable_get_chars( GtkEditable *editable,
- gint start_pos,
- gint end_pos );
-</verb></tscreen>
+ /* Now we specify that we want our newly created "menu" to be the menu
+ * for the "root menu" */
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
-This is a function of the parent class of the text widget. A value of -1 as
-<tt/end_pos/ signifies the end of the text. The index of the text starts at 0.
+ /* A vbox to put a menu and a button in: */
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+ gtk_widget_show(vbox);
-The function allocates a new chunk of memory for the text block, so don't forget
-to free it with a call to g_free when you have finished with it.
-
-<!-- ----------------------------------------------------------------- -->
-<sect1>Keyboard Shortcuts
-<p>
-The text widget has a number of pre-installed keyboard shotcuts for common
-editing, motion and selection functions. These are accessed using Control
-and Alt key combinations.
+ /* Create a menu-bar to hold the menus and add it to our main window */
+ menu_bar = gtk_menu_bar_new();
+ gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
+ gtk_widget_show(menu_bar);
-In addition to these, holding down the Control key whilst using cursor key
-movement will move the cursor by words rather than characters. Holding down
-Shift whilst using cursor movement will extend the selection.
+ /* Create a button to which to attach menu as a popup */
+ button = gtk_button_new_with_label("press me");
+ gtk_signal_connect_object(GTK_OBJECT(button), "event",
+ GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
+ gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
+ gtk_widget_show(button);
-<sect2>Motion Shotcuts
-<p>
-<itemize>
-<item> Ctrl-A Beginning of line
-<item> Ctrl-E End of line
-<item> Ctrl-N Next Line
-<item> Ctrl-P Previous Line
-<item> Ctrl-B Backward one character
-<item> Ctrl-F Forward one character
-<item> Alt-B Backward one word
-<item> Alt-F Forward one word
-</itemize>
+ /* And finally we append the menu-item to the menu-bar -- this is the
+ * "root" menu-item I have been raving about =) */
+ gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
-<sect2>Editing Shortcuts
-<p>
-<itemize>
-<item> Ctrl-H Delete Backward Character (Backspace)
-<item> Ctrl-D Delete Forward Character (Delete)
-<item> Ctrl-W Delete Backward Word
-<item> Alt-D Delete Forward Word
-<item> Ctrl-K Delete to end of line
-<item> Ctrl-U Delete line
-</itemize>
+ /* always display the window as the last step so it all splashes on
+ * the screen at once. */
+ gtk_widget_show(window);
-<sect2>Selection Shortcuts
-<p>
-<itemize>
-<item> Ctrl-X Cut to clipboard
-<item> Ctrl-C Copy to clipboard
-<item> Ctrl-V Paste from clipboard
-</itemize>
+ gtk_main ();
-<!-- ***************************************************************** -->
-<sect> Undocumented Widgets
-<!-- ***************************************************************** -->
-<p>
-These all require authors! :) Please consider contributing to our tutorial.
+ return 0;
+}
-If you must use one of these widgets that are undocumented, I strongly
-suggest you take a look at their respective header files in the GTK
-distribution. GTK's function names are very descriptive. Once you have an
-understanding of how things work, it's not difficult to figure out how to
-use a widget simply by looking at it's function declarations. This, along
-with a few examples from others' code, and it should be no problem.
+/* Respond to a button-press by posting a menu passed in as widget.
+ *
+ * Note that the "widget" argument is the menu being posted, NOT
+ * the button that was pressed.
+ */
-When you do come to understand all the functions of a new undocumented
-widget, please consider writing a tutorial on it so others may benifit
-from your time.
+static gint button_press (GtkWidget *widget, GdkEvent *event)
+{
-<!-- ----------------------------------------------------------------- -->
-<sect1> Adjustments
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Toolbar
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Fixed Container
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Range Controls
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Curves
-<p>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Previews
-<p>
+ if (event->type == GDK_BUTTON_PRESS) {
+ GdkEventButton *bevent = (GdkEventButton *) event;
+ gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
+ bevent->button, bevent->time);
+ /* Tell calling code that we have handled this event; the buck
+ * stops here. */
+ return TRUE;
+ }
-(This may need to be rewritten to follow the style of the rest of the tutorial)
+ /* Tell calling code that we have not handled this event; pass it on. */
+ return FALSE;
+}
-<tscreen><verb>
-Previews serve a number of purposes in GIMP/GTK. The most important one is
-this. High quality images may take up to tens of megabytes of memory - easy!
-Any operation on an image that big is bound to take a long time. If it takes
-you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after
-you make an error) to choose the desired modification, it make take you
-literally hours to make the right one - if you don't run out of memory
-first. People who have spent hours in color darkrooms know the feeling.
-Previews to the rescue!
+/* Print a string when a menu item is selected */
-But the annoyance of the delay is not the only issue. Oftentimes it is
-helpful to compare the Before and After versions side-by-side or at least
-back-to-back. If you're working with big images and 10 second delays,
-obtaining the Before and After impressions is, to say the least, difficult.
-For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right
-out for most people, while back-to-back is more like back-to-1001, 1002,
-..., 1010-back! Previews to the rescue!
+static void menuitem_response (gchar *string)
+{
+ printf("%s\n", string);
+}
+/* example-end */
+</verb></tscreen>
-But there's more. Previews allow for side-by-side pre-previews. In other
-words, you write a plug-in (e.g. the filterpack simulation) which would have
-a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
-An approach like this acts as a sort of a preview palette and is very
-effective fow subtle changes. Let's go previews!
+You may also set a menu item to be insensitive and, using an accelerator
+table, bind keys to menu functions.
-There's more. For certain plug-ins real-time image-specific human
-intervention maybe necessary. In the SuperNova plug-in, for example, the
-user is asked to enter the coordinates of the center of the future
-supernova. The easiest way to do this, really, is to present the user with a
-preview and ask him to intereactively select the spot. Let's go previews!
+<!-- ----------------------------------------------------------------- -->
+<sect1>Using GtkMenuFactory
+<p>
+Now that we've shown you the hard way, here's how you do it using the
+gtk_menu_factory calls.
-Finally, a couple of misc uses. One can use previews even when not working
-with big images. For example, they are useful when rendering compicated
-patterns. (Just check out the venerable Diffraction plug-in + many other
-ones!) As another example, take a look at the colormap rotation plug-in
-(work in progress). You can also use previews for little logo's inside you
-plug-ins and even for an image of yourself, The Author. Let's go previews!
+<!-- ----------------------------------------------------------------- -->
+<sect1>Menu Factory Example
+<p>
+Here is an example using the GTK menu factory. This is the first file,
+menufactory.h. We keep a separate menufactory.c and mfmain.c because
+of the global variables used in the menufactory.c file.
-When Not to Use Previews
+<tscreen><verb>
+/* example-start menu menufactory.h */
-Don't use previews for graphs, drawing etc. GDK is much faster for that. Use
-previews only for rendered images!
+#ifndef __MENUFACTORY_H__
+#define __MENUFACTORY_H__
-Let's go previews!
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-You can stick a preview into just about anything. In a vbox, an hbox, a
-table, a button, etc. But they look their best in tight frames around them.
-Previews by themselves do not have borders and look flat without them. (Of
-course, if the flat look is what you want...) Tight frames provide the
-necessary borders.
+void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
+void menus_create(GtkMenuEntry *entries, int nmenu_entries);
- [Image][Image]
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-Previews in many ways are like any other widgets in GTK (whatever that
-means) except they possess an addtional feature: they need to be filled with
-some sort of an image! First, we will deal exclusively with the GTK aspect
-of previews and then we'll discuss how to fill them.
+#endif /* __MENUFACTORY_H__ */
+/* example-end */
+</verb></tscreen>
-GtkWidget *preview!
+And here is the menufactory.c file.
-Without any ado:
+<tscreen><verb>
+/* example-start menu menufactory.c */
- /* Create a preview widget,
- set its size, an show it */
-GtkWidget *preview;
-preview=gtk_preview_new(GTK_PREVIEW_COLOR)
- /*Other option:
- GTK_PREVIEW_GRAYSCALE);*/
-gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
-gtk_widget_show(preview);
-my_preview_rendering_function(preview);
+#include <gtk/gtk.h>
+#include <strings.h>
-Oh yeah, like I said, previews look good inside frames, so how about:
+#include "mfmain.h"
-GtkWidget *create_a_preview(int Width,
- int Height,
- int Colorfulness)
-{
- GtkWidget *preview;
- GtkWidget *frame;
-
- frame = gtk_frame_new(NULL);
- gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
- gtk_container_border_width (GTK_CONTAINER(frame),0);
- gtk_widget_show(frame);
- preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
- :GTK_PREVIEW_GRAYSCALE);
- gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
- gtk_container_add(GTK_CONTAINER(frame),preview);
- gtk_widget_show(preview);
+static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
+static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
+void menus_init(void);
+void menus_create(GtkMenuEntry * entries, int nmenu_entries);
- my_preview_rendering_function(preview);
- return frame;
-}
-That's my basic preview. This routine returns the "parent" frame so you can
-place it somewhere else in your interface. Of course, you can pass the
-parent frame to this routine as a parameter. In many situations, however,
-the contents of the preview are changed continually by your application. In
-this case you may want to pass a pointer to the preview to a
-"create_a_preview()" and thus have control of it later.
+/* this is the GtkMenuEntry structure used to create new menus. The
+ * first member is the menu definition string. The second, the
+ * default accelerator key used to access this menu function with
+ * the keyboard. The third is the callback function to call when
+ * this menu item is selected (by the accelerator key, or with the
+ * mouse.) The last member is the data to pass to your callback function.
+ */
-One more important note that may one day save you a lot of time. Sometimes
-it is desirable to label you preview. For example, you may label the preview
-containing the original image as "Original" and the one containing the
-modified image as "Less Original". It might occure to you to pack the
-preview along with the appropriate label into a vbox. The unexpected caveat
-is that if the label is wider than the preview (which may happen for a
-variety of reasons unforseeable to you, from the dynamic decision on the
-size of the preview to the size of the font) the frame expands and no longer
-fits tightly over the preview. The same problem can probably arise in other
-situations as well.
+static GtkMenuEntry menu_items[] =
+{
+ {"<Main>/File/New", "<control>N", NULL, NULL},
+ {"<Main>/File/Open", "<control>O", NULL, NULL},
+ {"<Main>/File/Save", "<control>S", NULL, NULL},
+ {"<Main>/File/Save as", NULL, NULL, NULL},
+ {"<Main>/File/<separator>", NULL, NULL, NULL},
+ {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
+ {"<Main>/Options/Test", NULL, NULL, NULL}
+};
- [Image]
+/* calculate the number of menu_item's */
+static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
-The solution is to place the preview and the label into a 2x1 table and by
-attaching them with the following paramters (this is one possible variations
-of course. The key is no GTK_FILL in the second attachment):
+static int initialize = TRUE;
+static GtkMenuFactory *factory = NULL;
+static GtkMenuFactory *subfactory[1];
+static GHashTable *entry_ht = NULL;
-gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
- 0,
- GTK_EXPAND|GTK_FILL,
- 0,0);
-gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
- GTK_EXPAND,
- GTK_EXPAND,
- 0,0);
+void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
+{
+ if (initialize)
+ menus_init();
+
+ if (menubar)
+ *menubar = subfactory[0]->widget;
+ if (table)
+ *table = subfactory[0]->table;
+}
+
+void menus_init(void)
+{
+ if (initialize) {
+ initialize = FALSE;
+
+ factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
+ subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
+
+ gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
+ menus_create(menu_items, nmenu_items);
+ }
+}
+
+void menus_create(GtkMenuEntry * entries, int nmenu_entries)
+{
+ char *accelerator;
+ int i;
+
+ if (initialize)
+ menus_init();
+
+ if (entry_ht)
+ for (i = 0; i < nmenu_entries; i++) {
+ accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
+ if (accelerator) {
+ if (accelerator[0] == '\0')
+ entries[i].accelerator = NULL;
+ else
+ entries[i].accelerator = accelerator;
+ }
+ }
+ gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
+
+ for (i = 0; i < nmenu_entries; i++)
+ if (entries[i].widget) {
+ gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
+ (GtkSignalFunc) menus_install_accel,
+ entries[i].path);
+ gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
+ (GtkSignalFunc) menus_remove_accel,
+ entries[i].path);
+ }
+}
+
+static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
+{
+ char accel[64];
+ char *t1, t2[2];
+
+ accel[0] = '\0';
+ if (modifiers & GDK_CONTROL_MASK)
+ strcat(accel, "<control>");
+ if (modifiers & GDK_SHIFT_MASK)
+ strcat(accel, "<shift>");
+ if (modifiers & GDK_MOD1_MASK)
+ strcat(accel, "<alt>");
+
+ t2[0] = key;
+ t2[1] = '\0';
+ strcat(accel, t2);
+
+ if (entry_ht) {
+ t1 = g_hash_table_lookup(entry_ht, path);
+ g_free(t1);
+ } else
+ entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_hash_table_insert(entry_ht, path, g_strdup(accel));
+
+ return TRUE;
+}
+
+static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
+{
+ char *t;
+
+ if (entry_ht) {
+ t = g_hash_table_lookup(entry_ht, path);
+ g_free(t);
+
+ g_hash_table_insert(entry_ht, path, g_strdup(""));
+ }
+}
+
+void menus_set_sensitive(char *path, int sensitive)
+{
+ GtkMenuPath *menu_path;
+
+ if (initialize)
+ menus_init();
+
+ menu_path = gtk_menu_factory_find(factory, path);
+ if (menu_path)
+ gtk_widget_set_sensitive(menu_path->widget, sensitive);
+ else
+ g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path);
+}
+/* example-end */
+</verb></tscreen>
+
+And here's the mfmain.h
+
+<tscreen><verb>
+/* example-start menu mfmain.h */
+
+#ifndef __MFMAIN_H__
+#define __MFMAIN_H__
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MFMAIN_H__ */
+/* example-end */
+</verb></tscreen>
+
+And mfmain.c
+
+<tscreen><verb>
+/* example-start menu mfmain.c */
+
+#include <gtk/gtk.h>
+
+#include "mfmain.h"
+#include "menufactory.h"
+
+
+int main(int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *main_vbox;
+ GtkWidget *menubar;
+
+ GtkAcceleratorTable *accel;
+
+ gtk_init(&argc, &argv);
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(file_quit_cmd_callback),
+ "WM destroy");
+ gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
+ gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
+
+ main_vbox = gtk_vbox_new(FALSE, 1);
+ gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
+ gtk_container_add(GTK_CONTAINER(window), main_vbox);
+ gtk_widget_show(main_vbox);
+
+ get_main_menu(&menubar, &accel);
+ gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
+ gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
+ gtk_widget_show(menubar);
+
+ gtk_widget_show(window);
+ gtk_main();
+
+ return(0);
+}
+
+/* This is just to demonstrate how callbacks work when using the
+ * menufactory. Often, people put all the callbacks from the menus
+ * in a separate file, and then have them call the appropriate functions
+ * from there. Keeps it more organized. */
+void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
+{
+ g_print ("%s\n", (char *) data);
+ gtk_exit(0);
+}
+/* example-end */
+</verb></tscreen>
+
+And a makefile so it'll be easier to compile it.
+
+<tscreen><verb>
+# Makefile.mf
+
+CC = gcc
+PROF = -g
+C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
+L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
+L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
+PROGNAME = menufactory
+
+O_FILES = menufactory.o mfmain.o
+
+$(PROGNAME): $(O_FILES)
+ rm -f $(PROGNAME)
+ $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
+
+.c.o:
+ $(CC) -c $(C_FLAGS) $<
+
+clean:
+ rm -f core *.o $(PROGNAME) nohup.out
+distclean: clean
+ rm -f *~
+</verb></tscreen>
+
+For now, there's only this example. An explanation and lots 'o' comments
+will follow later.
+
+<!-- ***************************************************************** -->
+<sect> Text Widget
+<!-- ***************************************************************** -->
+<p>
+The Text widget allows multiple lines of text to be displayed and edited.
+It supports both multi-colored and multi-font text, allowing them to be
+mixed in any way we wish. It also has a wide set of key based text editing
+commands, which are compatible with Emacs.
+
+The text widget supports full cut-and-paste facilities, including the use
+of double- and triple-click to select a word and a whole line, respectively.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Creating and Configuring a Text box
+<p>
+There is only one function for creating a new Text widget.
+<tscreen><verb>
+GtkWidget *gtk_text_new( GtkAdjustment *hadj,
+ GtkAdjustment *vadj );
+</verb></tscreen>
+
+The arguments allow us to give the Text widget pointers to Adjustments
+that can be used to track the viewing position of the widget. Passing NULL
+values to either or both of these arguments will cause the gtk_text_new
+function to create it's own.
+
+<tscreen><verb>
+void gtk_text_set_adjustments( GtkText *text,
+ GtkAdjustment *hadj,
+ GtkAdjustment *vadj );
+</verb></tscreen>
+
+The above function allows the horizontal and vertical adjustments of a
+Text widget to be changed at any time.
+
+The text widget will not automatically create it's own scrollbars when
+the amount of text to be displayed is too long for the display window. We
+therefore have to create and add them to the display layout ourselves.
+
+<tscreen><verb>
+ vscrollbar = gtk_vscrollbar_new (GTK_TEXT(text)->vadj);
+ gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
+ gtk_widget_show (vscrollbar);
+</verb></tscreen>
+
+The above code snippet creates a new vertical scrollbar, and attaches
+it to the vertical adjustment of the text widget, <tt/text/. It then packs
+it into a box in the normal way.
+
+Note, currently the GtkText widget does not support horizontal scrollbars.
+
+There are two main ways in which a Text widget can be used: to allow the
+user to edit a body of text, or to allow us to display multiple lines of
+text to the user. In order for us to switch between these modes of
+operation, the text widget has the following function:
+
+<tscreen><verb>
+void gtk_text_set_editable( GtkText *text,
+ gint editable );
+</verb></tscreen>
+
+The <tt/editable/ argument is a TRUE or FALSE value that specifies whether
+the user is permitted to edit the contents of the Text widget. When the
+text widget is editable, it will display a cursor at the current insertion
+point.
+
+You are not, however, restricted to just using the text widget in these
+two modes. You can toggle the editable state of the text widget at any
+time, and can insert text at any time.
+
+The text widget wraps lines of text that are too long to
+fit onto a single line of the display window. It's default behaviour is
+to break words across line breaks. This can be changed using the next
+function:
+
+<tscreen><verb>
+void gtk_text_set_word_wrap( GtkText *text,
+ gint word_wrap );
+</verb></tscreen>
+
+Using this function allows us to specify that the text widget should
+wrap long lines on word boundaries. The <tt/word_wrap/ argument is a
+TRUE or FALSE value.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Text Manipulation
+<P>
+The current insertion point of a Text widget can be set using
+<tscreen><verb>
+void gtk_text_set_point( GtkText *text,
+ guint index );
+</verb></tscreen>
+
+where <tt/index/ is the position to set the insertion point.
+
+Analogous to this is the function for getting the current insertion point:
+
+<tscreen><verb>
+guint gtk_text_get_point( GtkText *text );
+</verb></tscreen>
+
+A function that is useful in combination with the above two functions is
+
+<tscreen><verb>
+guint gtk_text_get_length( GtkText *text );
+</verb></tscreen>
+
+which returns the current length of the Text widget. The length is the
+number of characters that are within the text block of the widget,
+including characters such as carriage-return, which marks the end of lines.
+
+In order to insert text at the current insertion point of a Text
+widget, the function gtk_text_insert is used, which also allows us to
+specify background and foreground colors and a font for the text.
+
+<tscreen><verb>
+void gtk_text_insert( GtkText *text,
+ GdkFont *font,
+ GdkColor *fore,
+ GdkColor *back,
+ const char *chars,
+ gint length );
+</verb></tscreen>
+
+Passing a value of <tt/NULL/ in as the value for the foreground color,
+background colour or font will result in the values set within the widget
+style to be used. Using a value of <tt/-1/ for the length parameter will
+result in the whole of the text string given being inserted.
+
+The text widget is one of the few within GTK that redraws itself
+dynamically, outside of the gtk_main function. This means that all changes
+to the contents of the text widget take effect immediately. This may be
+undesirable when performing multiple changes to the text widget. In order
+to allow us to perform multiple updates to the text widget without it
+continuously redrawing, we can freeze the widget, which temporarily stops
+it from automatically redrawing itself every time it is changed. We can
+then thaw the widget after our updates are complete.
+
+The following two functions perform this freeze and thaw action:
+
+<tscreen><verb>
+void gtk_text_freeze( GtkText *text );
+
+void gtk_text_thaw( GtkText *text );
+</verb></tscreen>
+
+Text is deleted from the text widget relative to the current insertion
+point by the following two functions. The return value is a TRUE or
+FALSE indicator of whether the operation was successful.
+
+<tscreen><verb>
+gint gtk_text_backward_delete( GtkText *text,
+ guint nchars );
+
+gint gtk_text_forward_delete ( GtkText *text,
+ guint nchars );
+</verb></tscreen>
+
+If you want to retrieve the contents of the text widget, then the macro
+<tt/GTK_TEXT_INDEX(t, index)/ allows you to retrieve the character at
+position <tt/index/ within the text widget <tt/t/.
+
+To retrieve larger blocks of text, we can use the function
+
+<tscreen><verb>
+gchar *gtk_editable_get_chars( GtkEditable *editable,
+ gint start_pos,
+ gint end_pos );
+</verb></tscreen>
+
+This is a function of the parent class of the text widget. A value of -1 as
+<tt/end_pos/ signifies the end of the text. The index of the text starts at 0.
+
+The function allocates a new chunk of memory for the text block, so don't forget
+to free it with a call to g_free when you have finished with it.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Keyboard Shortcuts
+<p>
+The text widget has a number of pre-installed keyboard shotcuts for common
+editing, motion and selection functions. These are accessed using Control
+and Alt key combinations.
+
+In addition to these, holding down the Control key whilst using cursor key
+movement will move the cursor by words rather than characters. Holding down
+Shift whilst using cursor movement will extend the selection.
+
+<sect2>Motion Shotcuts
+<p>
+<itemize>
+<item> Ctrl-A Beginning of line
+<item> Ctrl-E End of line
+<item> Ctrl-N Next Line
+<item> Ctrl-P Previous Line
+<item> Ctrl-B Backward one character
+<item> Ctrl-F Forward one character
+<item> Alt-B Backward one word
+<item> Alt-F Forward one word
+</itemize>
+
+<sect2>Editing Shortcuts
+<p>
+<itemize>
+<item> Ctrl-H Delete Backward Character (Backspace)
+<item> Ctrl-D Delete Forward Character (Delete)
+<item> Ctrl-W Delete Backward Word
+<item> Alt-D Delete Forward Word
+<item> Ctrl-K Delete to end of line
+<item> Ctrl-U Delete line
+</itemize>
+
+<sect2>Selection Shortcuts
+<p>
+<itemize>
+<item> Ctrl-X Cut to clipboard
+<item> Ctrl-C Copy to clipboard
+<item> Ctrl-V Paste from clipboard
+</itemize>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>A GtkText Example
+<p>
+<tscreen><verb>
+/* example-start text text.c */
+
+/* text.c */
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+
+void text_toggle_editable (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_editable(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void text_toggle_word_wrap (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_word_wrap(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void close_application( GtkWidget *widget, gpointer data )
+{
+ gtk_main_quit();
+}
+
+int main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *box1;
+ GtkWidget *box2;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *check;
+ GtkWidget *separator;
+ GtkWidget *table;
+ GtkWidget *vscrollbar;
+ GtkWidget *text;
+ GdkColormap *cmap;
+ GdkColor colour;
+ GdkFont *fixed_font;
+
+ FILE *infile;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_usize (window, 600, 500);
+ gtk_window_set_policy (GTK_WINDOW(window), TRUE, TRUE, FALSE);
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_window_set_title (GTK_WINDOW (window), "Text Widget Example");
+ gtk_container_border_width (GTK_CONTAINER (window), 0);
+
+
+ box1 = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), box1);
+ gtk_widget_show (box1);
+
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
+ gtk_widget_show (box2);
+
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2);
+ gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2);
+ gtk_box_pack_start (GTK_BOX (box2), table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* Create the GtkText widget */
+ text = gtk_text_new (NULL, NULL);
+ gtk_text_set_editable (GTK_TEXT (text), TRUE);
+ gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (text);
+
+ /* Add a vertical scrollbar to the GtkText widget */
+ vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
+ gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1,
+ GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (vscrollbar);
+
+ /* Get the system colour map and allocate the colour red */
+ cmap = gdk_colormap_get_system();
+ colour.red = 0xffff;
+ colour.green = 0;
+ colour.blue = 0;
+ if (!gdk_color_alloc(cmap, &colour)) {
+ g_error("couldn't allocate colour");
+ }
+
+ /* Load a fixed font */
+ fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*");
+
+ /* Realizing a widget creates a window for it, ready for us to insert some text */
+ gtk_widget_realize (text);
+
+ /* Freeze the text widget, ready for multiple updates */
+ gtk_text_freeze (GTK_TEXT (text));
+
+ /* Insert some coloured text */
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "Supports ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &colour, NULL,
+ "colored ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "text and different ", -1);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, &text->style->black, NULL,
+ "fonts\n\n", -1);
+
+ /* Load the file text.c into the text window */
+
+ infile = fopen("text.c", "r");
+
+ if (infile) {
+ char buffer[1024];
+ int nchars;
+
+ while (1)
+ {
+ nchars = fread(buffer, 1, 1024, infile);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, NULL,
+ NULL, buffer, nchars);
+
+ if (nchars < 1024)
+ break;
+ }
+
+ fclose (infile);
+ }
+
+ /* Thaw the text widget, allowing the updates to become visible */
+ gtk_text_thaw (GTK_TEXT (text));
+
+ hbox = gtk_hbutton_box_new ();
+ gtk_box_pack_start (GTK_BOX (box2), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ check = gtk_check_button_new_with_label("Editable");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, FALSE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_editable), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
+ gtk_widget_show (check);
+ check = gtk_check_button_new_with_label("Wrap Words");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, TRUE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_word_wrap), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), FALSE);
+ gtk_widget_show (check);
+
+ separator = gtk_hseparator_new ();
+ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0);
+ gtk_widget_show (separator);
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0);
+ gtk_widget_show (box2);
+
+ button = gtk_button_new_with_label ("close");
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_widget_grab_default (button);
+ gtk_widget_show (button);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
+
+
+<!-- ***************************************************************** -->
+<sect> Undocumented Widgets
+<!-- ***************************************************************** -->
+<p>
+These all require authors! :) Please consider contributing to our tutorial.
+
+If you must use one of these widgets that are undocumented, I strongly
+suggest you take a look at their respective header files in the GTK
+distribution. GTK's function names are very descriptive. Once you have an
+understanding of how things work, it's not difficult to figure out how to
+use a widget simply by looking at it's function declarations. This, along
+with a few examples from others' code, and it should be no problem.
+
+When you do come to understand all the functions of a new undocumented
+widget, please consider writing a tutorial on it so others may benifit
+from your time.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Adjustments
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Toolbar
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Fixed Container
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Range Controls
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Curves
+<p>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Previews
+<p>
+
+(This may need to be rewritten to follow the style of the rest of the tutorial)
+
+<tscreen><verb>
+
+Previews serve a number of purposes in GIMP/GTK. The most important one is
+this. High quality images may take up to tens of megabytes of memory - easy!
+Any operation on an image that big is bound to take a long time. If it takes
+you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after
+you make an error) to choose the desired modification, it make take you
+literally hours to make the right one - if you don't run out of memory
+first. People who have spent hours in color darkrooms know the feeling.
+Previews to the rescue!
+
+But the annoyance of the delay is not the only issue. Oftentimes it is
+helpful to compare the Before and After versions side-by-side or at least
+back-to-back. If you're working with big images and 10 second delays,
+obtaining the Before and After impressions is, to say the least, difficult.
+For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right
+out for most people, while back-to-back is more like back-to-1001, 1002,
+..., 1010-back! Previews to the rescue!
+
+But there's more. Previews allow for side-by-side pre-previews. In other
+words, you write a plug-in (e.g. the filterpack simulation) which would have
+a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
+An approach like this acts as a sort of a preview palette and is very
+effective fow subtle changes. Let's go previews!
+
+There's more. For certain plug-ins real-time image-specific human
+intervention maybe necessary. In the SuperNova plug-in, for example, the
+user is asked to enter the coordinates of the center of the future
+supernova. The easiest way to do this, really, is to present the user with a
+preview and ask him to intereactively select the spot. Let's go previews!
+
+Finally, a couple of misc uses. One can use previews even when not working
+with big images. For example, they are useful when rendering compicated
+patterns. (Just check out the venerable Diffraction plug-in + many other
+ones!) As another example, take a look at the colormap rotation plug-in
+(work in progress). You can also use previews for little logo's inside you
+plug-ins and even for an image of yourself, The Author. Let's go previews!
+
+When Not to Use Previews
+
+Don't use previews for graphs, drawing etc. GDK is much faster for that. Use
+previews only for rendered images!
+
+Let's go previews!
+
+You can stick a preview into just about anything. In a vbox, an hbox, a
+table, a button, etc. But they look their best in tight frames around them.
+Previews by themselves do not have borders and look flat without them. (Of
+course, if the flat look is what you want...) Tight frames provide the
+necessary borders.
+
+ [Image][Image]
+
+Previews in many ways are like any other widgets in GTK (whatever that
+means) except they possess an addtional feature: they need to be filled with
+some sort of an image! First, we will deal exclusively with the GTK aspect
+of previews and then we'll discuss how to fill them.
+
+GtkWidget *preview!
+
+Without any ado:
+
+ /* Create a preview widget,
+ set its size, an show it */
+GtkWidget *preview;
+preview=gtk_preview_new(GTK_PREVIEW_COLOR)
+ /*Other option:
+ GTK_PREVIEW_GRAYSCALE);*/
+gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
+gtk_widget_show(preview);
+my_preview_rendering_function(preview);
+
+Oh yeah, like I said, previews look good inside frames, so how about:
+
+GtkWidget *create_a_preview(int Width,
+ int Height,
+ int Colorfulness)
+{
+ GtkWidget *preview;
+ GtkWidget *frame;
+
+ frame = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_container_border_width (GTK_CONTAINER(frame),0);
+ gtk_widget_show(frame);
+
+ preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
+ :GTK_PREVIEW_GRAYSCALE);
+ gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
+ gtk_container_add(GTK_CONTAINER(frame),preview);
+ gtk_widget_show(preview);
+
+ my_preview_rendering_function(preview);
+ return frame;
+}
+
+That's my basic preview. This routine returns the "parent" frame so you can
+place it somewhere else in your interface. Of course, you can pass the
+parent frame to this routine as a parameter. In many situations, however,
+the contents of the preview are changed continually by your application. In
+this case you may want to pass a pointer to the preview to a
+"create_a_preview()" and thus have control of it later.
+
+One more important note that may one day save you a lot of time. Sometimes
+it is desirable to label you preview. For example, you may label the preview
+containing the original image as "Original" and the one containing the
+modified image as "Less Original". It might occure to you to pack the
+preview along with the appropriate label into a vbox. The unexpected caveat
+is that if the label is wider than the preview (which may happen for a
+variety of reasons unforseeable to you, from the dynamic decision on the
+size of the preview to the size of the font) the frame expands and no longer
+fits tightly over the preview. The same problem can probably arise in other
+situations as well.
+
+ [Image]
+
+The solution is to place the preview and the label into a 2x1 table and by
+attaching them with the following paramters (this is one possible variations
+of course. The key is no GTK_FILL in the second attachment):
+
+gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
+ 0,
+ GTK_EXPAND|GTK_FILL,
+ 0,0);
+gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
+ GTK_EXPAND,
+ GTK_EXPAND,
+ 0,0);
And here's the result:
- [Image]
+ [Image]
+
+Misc
+
+Making a preview clickable is achieved most easily by placing it in a
+button. It also adds a nice border around the preview and you may not even
+need to place it in a frame. See the Filter Pack Simulation plug-in for an
+example.
+
+This is pretty much it as far as GTK is concerned.
+
+Filling In a Preview
+
+In order to familiarize ourselves with the basics of filling in previews,
+let's create the following pattern (contrived by trial and error):
+
+ [Image]
+
+void
+my_preview_rendering_function(GtkWidget *preview)
+{
+#define SIZE 100
+#define HALF (SIZE/2)
+
+ guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
+ gint i, j; /* Coordinates */
+ double r, alpha, x, y;
+
+ if (preview==NULL) return; /* I usually add this when I want */
+ /* to avoid silly crashes. You */
+ /* should probably make sure that */
+ /* everything has been nicely */
+ /* initialized! */
+ for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
+ /* glib.h contains ABS(x). */
+ row[i*3+0] = sqrt(1-r)*255; /* Define Red */
+ row[i*3+1] = 128; /* Define Green */
+ row[i*3+2] = 224; /* Define Blue */
+ } /* "+0" is for alignment! */
+ else {
+ row[i*3+0] = r*255;
+ row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
+ row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
+ }
+ }
+ gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
+ /* Insert "row" into "preview" starting at the point with */
+ /* coordinates (0,j) first column, j_th row extending SIZE */
+ /* pixels to the right */
+ }
+
+ free(row); /* save some space */
+ gtk_widget_draw(preview,NULL); /* what does this do? */
+ gdk_flush(); /* or this? */
+}
+
+Non-GIMP users can have probably seen enough to do a lot of things already.
+For the GIMP users I have a few pointers to add.
+
+Image Preview
+
+It is probably wize to keep a reduced version of the image around with just
+enough pixels to fill the preview. This is done by selecting every n'th
+pixel where n is the ratio of the size of the image to the size of the
+preview. All further operations (including filling in the previews) are then
+performed on the reduced number of pixels only. The following is my
+implementation of reducing the image. (Keep in mind that I've had only basic
+C!)
+
+(UNTESTED CODE ALERT!!!)
+
+typedef struct {
+ gint width;
+ gint height;
+ gint bbp;
+ guchar *rgb;
+ guchar *mask;
+} ReducedImage;
+
+enum {
+ SELECTION_ONLY,
+ SELCTION_IN_CONTEXT,
+ ENTIRE_IMAGE
+};
+
+ReducedImage *Reduce_The_Image(GDrawable *drawable,
+ GDrawable *mask,
+ gint LongerSize,
+ gint Selection)
+{
+ /* This function reduced the image down to the the selected preview size */
+ /* The preview size is determine by LongerSize, i.e. the greater of the */
+ /* two dimentions. Works for RGB images only! */
+ gint RH, RW; /* Reduced height and reduced width */
+ gint width, height; /* Width and Height of the area being reduced */
+ gint bytes=drawable->bpp;
+ ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
+
+ guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
+ gint i, j, whichcol, whichrow, x1, x2, y1, y2;
+ GPixelRgn srcPR, srcMask;
+ gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
+ /* image. */
+
+ gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
+ width = x2-x1;
+ height = y2-y1;
+ /* If there's a SELECTION, we got its bounds!)
+
+ if (width != drawable->width && height != drawable->height)
+ NoSelectionMade=FALSE;
+ /* Become aware of whether the user has made an active selection */
+ /* This will become important later, when creating a reduced mask. */
+
+ /* If we want to preview the entire image, overrule the above! */
+ /* Of course, if no selection has been made, this does nothing! */
+ if (Selection==ENTIRE_IMAGE) {
+ x1=0;
+ x2=drawable->width;
+ y1=0;
+ y2=drawable->height;
+ }
+
+ /* If we want to preview a selection with some surronding area we */
+ /* have to expand it a little bit. Consider it a bit of a riddle. */
+ if (Selection==SELECTION_IN_CONTEXT) {
+ x1=MAX(0, x1-width/2.0);
+ x2=MIN(drawable->width, x2+width/2.0);
+ y1=MAX(0, y1-height/2.0);
+ y2=MIN(drawable->height, y2+height/2.0);
+ }
+
+ /* How we can determine the width and the height of the area being */
+ /* reduced. */
+ width = x2-x1;
+ height = y2-y1;
+
+ /* The lines below determine which dimension is to be the longer */
+ /* side. The idea borrowed from the supernova plug-in. I suspect I */
+ /* could've thought of it myself, but the truth must be told. */
+ /* Plagiarism stinks! */
+ if (width>height) {
+ RW=LongerSize;
+ RH=(float) height * (float) LongerSize/ (float) width;
+ }
+ else {
+ RH=LongerSize;
+ RW=(float)width * (float) LongerSize/ (float) height;
+ }
+
+ /* The intire image is stretched into a string! */
+ tempRGB = (guchar *) malloc(RW*RH*bytes);
+ tempmask = (guchar *) malloc(RW*RH);
+
+ gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
+ gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
+
+ /* Grab enough to save a row of image and a row of mask. */
+ src_row = (guchar *) malloc (width*bytes);
+ src_mask_row = (guchar *) malloc (width);
+
+ for (i=0; i < RH; i++) {
+ whichrow=(float)i*(float)height/(float)RH;
+ gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
+ gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
+
+ for (j=0; j < RW; j++) {
+ whichcol=(float)j*(float)width/(float)RW;
+
+ /* No selection made = each point is completely selected! */
+ if (NoSelectionMade)
+ tempmask[i*RW+j]=255;
+ else
+ tempmask[i*RW+j]=src_mask_row[whichcol];
+
+ /* Add the row to the one long string which now contains the image! */
+ tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
+ tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
+ tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
+
+ /* Hold on to the alpha as well */
+ if (bytes==4)
+ tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
+ }
+ }
+ temp->bpp=bytes;
+ temp->width=RW;
+ temp->height=RH;
+ temp->rgb=tempRGB;
+ temp->mask=tempmask;
+ return temp;
+}
+
+The following is a preview function which used the same ReducedImage type!
+Note that it uses fakes transparancy (if one is present by means of
+fake_transparancy which is defined as follows:
+
+gint fake_transparency(gint i, gint j)
+{
+ if ( ((i%20)- 10) * ((j%20)- 10)>0 )
+ return 64;
+ else
+ return 196;
+}
+
+Now here's the preview function:
+
+void
+my_preview_render_function(GtkWidget *preview,
+ gint changewhat,
+ gint changewhich)
+{
+ gint Inten, bytes=drawable->bpp;
+ gint i, j, k;
+ float partial;
+ gint RW=reduced->width;
+ gint RH=reduced->height;
+ guchar *row=malloc(bytes*RW);;
+
+
+ for (i=0; i < RH; i++) {
+ for (j=0; j < RW; j++) {
+
+ row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
+ row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
+ row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
+
+ if (bytes==4)
+ for (k=0; k<3; k++) {
+ float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
+ row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
+ }
+ }
+ gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
+ }
+
+ free(a);
+ gtk_widget_draw(preview,NULL);
+ gdk_flush();
+}
+
+Applicable Routines
+
+guint gtk_preview_get_type (void);
+/* No idea */
+void gtk_preview_uninit (void);
+/* No idea */
+GtkWidget* gtk_preview_new (GtkPreviewType type);
+/* Described above */
+void gtk_preview_size (GtkPreview *preview,
+ gint width,
+ gint height);
+/* Allows you to resize an existing preview. */
+/* Apparantly there's a bug in GTK which makes */
+/* this process messy. A way to clean up a mess */
+/* is to manually resize the window containing */
+/* the preview after resizing the preview. */
+
+void gtk_preview_put (GtkPreview *preview,
+ GdkWindow *window,
+ GdkGC *gc,
+ gint srcx,
+ gint srcy,
+ gint destx,
+ gint desty,
+ gint width,
+ gint height);
+/* No idea */
+
+void gtk_preview_put_row (GtkPreview *preview,
+ guchar *src,
+ guchar *dest,
+ gint x,
+ gint y,
+ gint w);
+/* No idea */
+
+void gtk_preview_draw_row (GtkPreview *preview,
+ guchar *data,
+ gint x,
+ gint y,
+ gint w);
+/* Described in the text */
+
+void gtk_preview_set_expand (GtkPreview *preview,
+ gint expand);
+/* No idea */
+
+/* No clue for any of the below but */
+/* should be standard for most widgets */
+void gtk_preview_set_gamma (double gamma);
+void gtk_preview_set_color_cube (guint nred_shades,
+ guint ngreen_shades,
+ guint nblue_shades,
+ guint ngray_shades);
+void gtk_preview_set_install_cmap (gint install_cmap);
+void gtk_preview_set_reserved (gint nreserved);
+GdkVisual* gtk_preview_get_visual (void);
+GdkColormap* gtk_preview_get_cmap (void);
+GtkPreviewInfo* gtk_preview_get_info (void);
+
+That's all, folks!
+
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>The EventBox Widget<label id="sec_The_EventBox_Widget">
+<!-- ***************************************************************** -->
+<p>
+Some gtk widgets don't have associated X windows, so they just draw on
+their parents. Because of this, they cannot recieve events
+and if they are incorrectly sized, they don't clip so you can get
+messy overwritting etc. If you require more from these widgets, the
+EventBox is for you.
+
+At first glance, the EventBox widget might appear to be totally
+useless. It draws nothing on the screen and responds to no
+events. However, it does serve a function - it provides an X window for
+its child widget. This is important as many GTK widgets do not
+have an associated X window. Not having an X window saves memory and
+improves performance, but also has some drawbacks. A widget without an
+X window cannot receive events, and does not perform any clipping on
+it's contents. Although the name <em/EventBox/ emphasizes the
+event-handling function, the widget can also be used for clipping.
+(And more ... see the example below.)
+
+To create a new EventBox widget, use:
+
+<tscreen><verb>
+GtkWidget *gtk_event_box_new( void );
+</verb></tscreen>
+
+A child widget can then be added to this EventBox:
+
+<tscreen><verb>
+gtk_container_add( GTK_CONTAINER(event_box), widget );
+</verb></tscreen>
+
+The following example demonstrates both uses of an EventBox - a label
+is created that is clipped to a small box, and set up so that a
+mouse-click on the label causes the program to exit.
+
+<tscreen><verb>
+/* example-start eventbox eventbox.c */
+
+#include <gtk/gtk.h>
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *event_box;
+ GtkWidget *label;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
+
+ /* Create an EventBox and add it to our toplevel window */
+
+ event_box = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER(window), event_box);
+ gtk_widget_show (event_box);
+
+ /* Create a long label */
+
+ label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
+ gtk_container_add (GTK_CONTAINER (event_box), label);
+ gtk_widget_show (label);
+
+ /* Clip it short. */
+ gtk_widget_set_usize (label, 110, 20);
+
+ /* And bind an action to it */
+ gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
+ gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ /* Yet one more thing you need an X window for ... */
+
+ gtk_widget_realize (event_box);
+ gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes">
+<!-- ***************************************************************** -->
+<p>
+This describes the functions used to operate on widgets. These can be used
+to set style, padding, size etc.
+
+(Maybe I should make a whole section on accelerators.)
+
+<tscreen><verb>
+void gtk_widget_install_accelerator( GtkWidget *widget,
+ GtkAcceleratorTable *table,
+ gchar *signal_name,
+ gchar key,
+ guint8 modifiers );
+
+void gtk_widget_remove_accelerator ( GtkWidget *widget,
+ GtkAcceleratorTable *table,
+ gchar *signal_name);
+
+void gtk_widget_activate( GtkWidget *widget );
+
+void gtk_widget_set_name( GtkWidget *widget,
+ gchar *name );
+
+gchar *gtk_widget_get_name( GtkWidget *widget );
+
+void gtk_widget_set_sensitive( GtkWidget *widget,
+ gint sensitive );
+
+void gtk_widget_set_style( GtkWidget *widget,
+ GtkStyle *style );
+
+GtkStyle *gtk_widget_get_style( GtkWidget *widget );
+
+GtkStyle *gtk_widget_get_default_style( void );
+
+void gtk_widget_set_uposition( GtkWidget *widget,
+ gint x,
+ gint y );
+
+void gtk_widget_set_usize( GtkWidget *widget,
+ gint width,
+ gint height );
+
+void gtk_widget_grab_focus( GtkWidget *widget );
+
+void gtk_widget_show( GtkWidget *widget );
+
+void gtk_widget_hide( GtkWidget *widget );
+</verb></tscreen>
+
+<!-- ***************************************************************** -->
+<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts">
+<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Timeouts
+<p>
+You may be wondering how you make GTK do useful work when in gtk_main.
+Well, you have several options. Using the following functions you can
+create a timeout function that will be called every "interval"
+milliseconds.
+
+<tscreen><verb>
+gint gtk_timeout_add( guint32 interval,
+ GtkFunction function,
+ gpointer data );
+</verb></tscreen>
+
+The first argument is the number of milliseconds between calls to your
+function. The second argument is the function you wish to have called, and
+the third, the data passed to this callback function. The return value is
+an integer "tag" which may be used to stop the timeout by calling:
+
+<tscreen><verb>
+void gtk_timeout_remove( gint tag );
+</verb></tscreen>
+
+You may also stop the timeout function by returning zero or FALSE from
+your callback function. Obviously this means if you want your function to
+continue to be called, it should return a non-zero value, ie TRUE.
+
+The declaration of your callback should look something like this:
+
+<tscreen><verb>
+gint timeout_callback( gpointer data );
+</verb></tscreen>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Monitoring IO
+<p>
+Another nifty feature of GTK, is the ability to have it check for data on a
+file descriptor for you (as returned by open(2) or socket(2)). This is
+especially useful for networking applications. The function:
+
+<tscreen><verb>
+gint gdk_input_add( gint source,
+ GdkInputCondition condition,
+ GdkInputFunction function,
+ gpointer data );
+</verb></tscreen>
+
+Where the first argument is the file descriptor you wish to have watched,
+and the second specifies what you want GDK to look for. This may be one of:
+
+<itemize>
+<item>GDK_INPUT_READ - Call your function when there is data ready for
+reading on your file descriptor.
+
+<item>GDK_INPUT_WRITE - Call your function when the file descriptor is
+ready for writing.
+</itemize>
+
+As I'm sure you've figured out already, the third argument is the function
+you wish to have called when the above conditions are satisfied, and the
+fourth is the data to pass to this function.
+
+The return value is a tag that may be used to stop GDK from monitoring this
+file descriptor using the following function.
+
+<tscreen><verb>
+void gdk_input_remove( gint tag );
+</verb></tscreen>
+
+The callback function should be declared as:
+
+<tscreen><verb>
+void input_callback( gpointer data,
+ gint source,
+ GdkInputCondition condition );
+</verb></tscreen>
+
+Where <tt/source/ and <tt/condition/ are as specified above.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Idle Functions
+<p>
+<!-- Need to check on idle priorities - TRG -->
+What if you have a function you want called when nothing else is
+happening ?
+
+<tscreen><verb>
+gint gtk_idle_add( GtkFunction function,
+ gpointer data );
+</verb></tscreen>
+
+This causes GTK to call the specified function whenever nothing else is
+happening.
+
+<tscreen><verb>
+void gtk_idle_remove( gint tag );
+</verb></tscreen>
+
+I won't explain the meaning of the arguments as they follow very much like
+the ones above. The function pointed to by the first argument to
+gtk_idle_add will be called whenever the opportunity arises. As with the
+others, returning FALSE will stop the idle function from being called.
+
+<!-- ***************************************************************** -->
+<sect>Managing Selections
+<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
+<p>
+One type of interprocess communication supported by GTK is
+<em>selections</em>. A selection identifies a chunk of data, for
+instance, a portion of text, selected by the user in some fashion, for
+instance, by dragging with the mouse. Only one application on a
+display, (the <em>owner</em> can own a particular selection at one
+time, so when a selection is claimed by one application, the previous
+owner must indicate to the user that selection has been
+relinquished. Other applications can request the contents of a
+selection in different forms, called <em>targets</em>. There can be
+any number of selections, but most X applications only handle one, the
+<em>primary selection</em>.
+
+In most cases, it isn't necessary for a GTK application to deal with
+selections itself. The standard widgets, such as the Entry widget,
+already have the capability to claim the selection when appropriate
+(e.g., when the user drags over text), and to retrieve the contents of
+the selection owned by another widget, or another application (e.g.,
+when the user clicks the second mouse button). However, there may be
+cases in which you want to give other widgets the ability to supply
+the selection, or you wish to retrieve targets not supported by
+default.
-Misc
+A fundamental concept needed to understand selection handling is that
+of the <em>atom</em>. An atom is an integer that uniquely identifies a
+string (on a certain display). Certain atoms are predefined by the X
+server, and in some cases there are constants in <tt>gtk.h</tt>
+corresponding to these atoms. For instance the constant
+<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY".
+In other cases, you should use the functions
+<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string,
+and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both
+selections and targets are identifed by atoms.
-Making a preview clickable is achieved most easily by placing it in a
-button. It also adds a nice border around the preview and you may not even
-need to place it in a frame. See the Filter Pack Simulation plug-in for an
-example.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Retrieving the selection
+<p>
+Retrieving the selection is an asynchronous process. To start the
+process, you call:
-This is pretty much it as far as GTK is concerned.
+<tscreen><verb>
+gint gtk_selection_convert( GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target,
+ guint32 time );
+</verb</tscreen>
-Filling In a Preview
+This <em>converts</em> the selection into the form specified by
+<tt/target/. If at all possible, the time field should be the time
+from the event that triggered the selection. This helps make sure that
+events occur in the order that the user requested them. However, if it
+is not available (for instance, if the conversion was triggered by
+a "clicked" signal), then you can use the constant
+<tt>GDK_CURRENT_TIME</tt>.
-In order to familiarize ourselves with the basics of filling in previews,
-let's create the following pattern (contrived by trial and error):
+When the selection owner responds to the request, a
+"selection_received" signal is sent to your application. The handler
+for this signal receives a pointer to a <tt>GtkSelectionData</tt>
+structure, which is defined as:
- [Image]
+<tscreen><verb>
+struct _GtkSelectionData
+{
+ GdkAtom selection;
+ GdkAtom target;
+ GdkAtom type;
+ gint format;
+ guchar *data;
+ gint length;
+};
+</verb></tscreen>
+
+<tt>selection</tt> and <tt>target</tt> are the values you gave in your
+<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that
+identifies the type of data returned by the selection owner. Some
+possible values are "STRING", a string of latin-1 characters, "ATOM",
+a series of atoms, "INTEGER", an integer, etc. Most targets can only
+return one type. <tt/format/ gives the length of the units (for
+instance characters) in bits. Usually, you don't care about this when
+receiving data. <tt>data</tt> is a pointer to the returned data, and
+<tt>length</tt> gives the length of the returned data, in bytes. If
+<tt>length</tt> is negative, then an error occurred and the selection
+could not be retrieved. This might happen if no application owned the
+selection, or if you requested a target that the application didn't
+support. The buffer is actually guaranteed to be one byte longer than
+<tt>length</tt>; the extra byte will always be zero, so it isn't
+necessary to make a copy of strings just to null terminate them.
+
+In the following example, we retrieve the special target "TARGETS",
+which is a list of all targets into which the selection can be
+converted.
+
+<tscreen><verb>
+/* example-start selection gettargets.c */
+
+#include <gtk/gtk.h>
+void selection_received (GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data);
+
+/* Signal handler invoked when user clicks on the "Get Targets" button */
void
-my_preview_rendering_function(GtkWidget *preview)
+get_targets (GtkWidget *widget, gpointer data)
{
-#define SIZE 100
-#define HALF (SIZE/2)
+ static GdkAtom targets_atom = GDK_NONE;
- guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
- gint i, j; /* Coordinates */
- double r, alpha, x, y;
+ /* Get the atom corresonding to the string "TARGETS" */
+ if (targets_atom == GDK_NONE)
+ targets_atom = gdk_atom_intern ("TARGETS", FALSE);
- if (preview==NULL) return; /* I usually add this when I want */
- /* to avoid silly crashes. You */
- /* should probably make sure that */
- /* everything has been nicely */
- /* initialized! */
- for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
- /* glib.h contains ABS(x). */
- row[i*3+0] = sqrt(1-r)*255; /* Define Red */
- row[i*3+1] = 128; /* Define Green */
- row[i*3+2] = 224; /* Define Blue */
- } /* "+0" is for alignment! */
- else {
- row[i*3+0] = r*255;
- row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
- row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
- }
+ /* And request the "TARGETS" target for the primary selection */
+ gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
+ GDK_CURRENT_TIME);
+}
+
+/* Signal handler called when the selections owner returns the data */
+void
+selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
+ gpointer data)
+{
+ GdkAtom *atoms;
+ GList *item_list;
+ int i;
+
+ /* **** IMPORTANT **** Check to see if retrieval succeeded */
+ if (selection_data->length < 0)
+ {
+ g_print ("Selection retrieval failed\n");
+ return;
}
- gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
- /* Insert "row" into "preview" starting at the point with */
- /* coordinates (0,j) first column, j_th row extending SIZE */
- /* pixels to the right */
- }
+ /* Make sure we got the data in the expected form */
+ if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
+ {
+ g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
+ return;
+ }
+
+ /* Print out the atoms we received */
+ atoms = (GdkAtom *)selection_data->data;
- free(row); /* save some space */
- gtk_widget_draw(preview,NULL); /* what does this do? */
- gdk_flush(); /* or this? */
+ item_list = NULL;
+ for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
+ {
+ char *name;
+ name = gdk_atom_name (atoms[i]);
+ if (name != NULL)
+ g_print ("%s\n",name);
+ else
+ g_print ("(bad atom)\n");
+ }
+
+ return;
}
-Non-GIMP users can have probably seen enough to do a lot of things already.
-For the GIMP users I have a few pointers to add.
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *button;
+
+ gtk_init (&argc, &argv);
-Image Preview
+ /* Create the toplevel window */
-It is probably wize to keep a reduced version of the image around with just
-enough pixels to fill the preview. This is done by selecting every n'th
-pixel where n is the ratio of the size of the image to the size of the
-preview. All further operations (including filling in the previews) are then
-performed on the reduced number of pixels only. The following is my
-implementation of reducing the image. (Keep in mind that I've had only basic
-C!)
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-(UNTESTED CODE ALERT!!!)
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
-typedef struct {
- gint width;
- gint height;
- gint bbp;
- guchar *rgb;
- guchar *mask;
-} ReducedImage;
+ /* Create a button the user can click to get targets */
-enum {
- SELECTION_ONLY,
- SELCTION_IN_CONTEXT,
- ENTIRE_IMAGE
-};
+ button = gtk_button_new_with_label ("Get Targets");
+ gtk_container_add (GTK_CONTAINER (window), button);
-ReducedImage *Reduce_The_Image(GDrawable *drawable,
- GDrawable *mask,
- gint LongerSize,
- gint Selection)
-{
- /* This function reduced the image down to the the selected preview size */
- /* The preview size is determine by LongerSize, i.e. the greater of the */
- /* two dimentions. Works for RGB images only! */
- gint RH, RW; /* Reduced height and reduced width */
- gint width, height; /* Width and Height of the area being reduced */
- gint bytes=drawable->bpp;
- ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
+ gtk_signal_connect (GTK_OBJECT(button), "clicked",
+ GTK_SIGNAL_FUNC (get_targets), NULL);
+ gtk_signal_connect (GTK_OBJECT(button), "selection_received",
+ GTK_SIGNAL_FUNC (selection_received), NULL);
- guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
- gint i, j, whichcol, whichrow, x1, x2, y1, y2;
- GPixelRgn srcPR, srcMask;
- gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
- /* image. */
+ gtk_widget_show (button);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
+</verb></tscreen>
- gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
- width = x2-x1;
- height = y2-y1;
- /* If there's a SELECTION, we got its bounds!)
+<!-- ----------------------------------------------------------------- -->
+<sect1> Supplying the selection
+<p>
+Supplying the selection is a bit more complicated. You must register
+handlers that will be called when your selection is requested. For
+each selection/target pair you will handle, you make a call to:
- if (width != drawable->width && height != drawable->height)
- NoSelectionMade=FALSE;
- /* Become aware of whether the user has made an active selection */
- /* This will become important later, when creating a reduced mask. */
+<tscreen><verb>
+void gtk_selection_add_handler( GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target,
+ GtkSelectionFunction function,
+ GtkRemoveFunction remove_func,
+ gpointer data );
+</verb></tscreen>
- /* If we want to preview the entire image, overrule the above! */
- /* Of course, if no selection has been made, this does nothing! */
- if (Selection==ENTIRE_IMAGE) {
- x1=0;
- x2=drawable->width;
- y1=0;
- y2=drawable->height;
- }
+<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
+this handler will manage. <tt/remove_func/, if not
+NULL, will be called when the signal handler is removed. This is
+useful, for instance, for interpreted languages which need to
+keep track of a reference count for <tt/data/.
- /* If we want to preview a selection with some surronding area we */
- /* have to expand it a little bit. Consider it a bit of a riddle. */
- if (Selection==SELECTION_IN_CONTEXT) {
- x1=MAX(0, x1-width/2.0);
- x2=MIN(drawable->width, x2+width/2.0);
- y1=MAX(0, y1-height/2.0);
- y2=MIN(drawable->height, y2+height/2.0);
- }
+The callback function has the signature:
- /* How we can determine the width and the height of the area being */
- /* reduced. */
- width = x2-x1;
- height = y2-y1;
+<tscreen><verb>
+typedef void (*GtkSelectionFunction)( GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data );
- /* The lines below determine which dimension is to be the longer */
- /* side. The idea borrowed from the supernova plug-in. I suspect I */
- /* could've thought of it myself, but the truth must be told. */
- /* Plagiarism stinks! */
- if (width>height) {
- RW=LongerSize;
- RH=(float) height * (float) LongerSize/ (float) width;
- }
- else {
- RH=LongerSize;
- RW=(float)width * (float) LongerSize/ (float) height;
- }
+</verb></tscreen>
- /* The intire image is stretched into a string! */
- tempRGB = (guchar *) malloc(RW*RH*bytes);
- tempmask = (guchar *) malloc(RW*RH);
+The GtkSelectionData is the same as above, but this time, we're
+responsible for filling in the fields <tt/type/, <tt/format/,
+<tt/data/, and <tt/length/. (The <tt/format/ field is actually
+important here - the X server uses it to figure out whether the data
+needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a
+character - or 32 - <em/i.e./ a. integer.) This is done by calling the
+function:
- gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
- gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
+<tscreen><verb>
+void gtk_selection_data_set( GtkSelectionData *selection_data,
+ GdkAtom type,
+ gint format,
+ guchar *data,
+ gint length );
+</verb></tscreen>
- /* Grab enough to save a row of image and a row of mask. */
- src_row = (guchar *) malloc (width*bytes);
- src_mask_row = (guchar *) malloc (width);
+This function takes care of properly making a copy of the data so that
+you don't have to worry about keeping it around. (You should not fill
+in the fields of the GtkSelectionData structure by hand.)
- for (i=0; i < RH; i++) {
- whichrow=(float)i*(float)height/(float)RH;
- gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
- gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
+When prompted by the user, you claim ownership of the selection by
+calling:
- for (j=0; j < RW; j++) {
- whichcol=(float)j*(float)width/(float)RW;
+<tscreen><verb>
+gint gtk_selection_owner_set( GtkWidget *widget,
+ GdkAtom selection,
+ guint32 time );
+</verb></tscreen>
- /* No selection made = each point is completely selected! */
- if (NoSelectionMade)
- tempmask[i*RW+j]=255;
- else
- tempmask[i*RW+j]=src_mask_row[whichcol];
+If another application claims ownership of the selection, you will
+receive a "selection_clear_event".
- /* Add the row to the one long string which now contains the image! */
- tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
- tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
- tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
+As an example of supplying the selection, the following program adds
+selection functionality to a toggle button. When the toggle button is
+depressed, the program claims the primary selection. The only target
+supported (aside from certain targets like "TARGETS" supplied by GTK
+itself), is the "STRING" target. When this target is requested, a
+string representation of the time is returned.
- /* Hold on to the alpha as well */
- if (bytes==4)
- tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
- }
- }
- temp->bpp=bytes;
- temp->width=RW;
- temp->height=RH;
- temp->rgb=tempRGB;
- temp->mask=tempmask;
- return temp;
-}
+<tscreen><verb>
+/* example-start selection setselection.c */
-The following is a preview function which used the same ReducedImage type!
-Note that it uses fakes transparancy (if one is present by means of
-fake_transparancy which is defined as follows:
+#include <gtk/gtk.h>
+#include <time.h>
-gint fake_transparency(gint i, gint j)
+/* Callback when the user toggles the selection */
+void
+selection_toggled (GtkWidget *widget, gint *have_selection)
{
- if ( ((i%20)- 10) * ((j%20)- 10)>0 )
- return 64;
+ if (GTK_TOGGLE_BUTTON(widget)->active)
+ {
+ *have_selection = gtk_selection_owner_set (widget,
+ GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME);
+ /* if claiming the selection failed, we return the button to
+ the out state */
+ if (!*have_selection)
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
+ }
else
- return 196;
+ {
+ if (*have_selection)
+ {
+ /* Before clearing the selection by setting the owner to NULL,
+ we check if we are the actual owner */
+ if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
+ gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME);
+ *have_selection = FALSE;
+ }
+ }
}
-Now here's the preview function:
-
-void
-my_preview_render_function(GtkWidget *preview,
- gint changewhat,
- gint changewhich)
+/* Called when another application claims the selection */
+gint
+selection_clear (GtkWidget *widget, GdkEventSelection *event,
+ gint *have_selection)
{
- gint Inten, bytes=drawable->bpp;
- gint i, j, k;
- float partial;
- gint RW=reduced->width;
- gint RH=reduced->height;
- guchar *row=malloc(bytes*RW);;
-
+ *have_selection = FALSE;
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
- for (i=0; i < RH; i++) {
- for (j=0; j < RW; j++) {
+ return TRUE;
+}
- row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
- row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
- row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
+/* Supplies the current time as the selection. */
+void
+selection_handle (GtkWidget *widget,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ gchar *timestr;
+ time_t current_time;
- if (bytes==4)
- for (k=0; k<3; k++) {
- float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
- row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
- }
- }
- gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
- }
+ current_time = time (NULL);
+ timestr = asctime (localtime(&current_time));
+ /* When we return a single string, it should not be null terminated.
+ That will be done for us */
- free(a);
- gtk_widget_draw(preview,NULL);
- gdk_flush();
+ gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
+ 8, timestr, strlen(timestr));
}
-Applicable Routines
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
-guint gtk_preview_get_type (void);
-/* No idea */
-void gtk_preview_uninit (void);
-/* No idea */
-GtkWidget* gtk_preview_new (GtkPreviewType type);
-/* Described above */
-void gtk_preview_size (GtkPreview *preview,
- gint width,
- gint height);
-/* Allows you to resize an existing preview. */
-/* Apparantly there's a bug in GTK which makes */
-/* this process messy. A way to clean up a mess */
-/* is to manually resize the window containing */
-/* the preview after resizing the preview. */
+ GtkWidget *selection_button;
-void gtk_preview_put (GtkPreview *preview,
- GdkWindow *window,
- GdkGC *gc,
- gint srcx,
- gint srcy,
- gint destx,
- gint desty,
- gint width,
- gint height);
-/* No idea */
+ static int have_selection = FALSE;
+
+ gtk_init (&argc, &argv);
-void gtk_preview_put_row (GtkPreview *preview,
- guchar *src,
- guchar *dest,
- gint x,
- gint y,
- gint w);
-/* No idea */
+ /* Create the toplevel window */
-void gtk_preview_draw_row (GtkPreview *preview,
- guchar *data,
- gint x,
- gint y,
- gint w);
-/* Described in the text */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Event Box");
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-void gtk_preview_set_expand (GtkPreview *preview,
- gint expand);
-/* No idea */
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ /* Create a toggle button to act as the selection */
-/* No clue for any of the below but */
-/* should be standard for most widgets */
-void gtk_preview_set_gamma (double gamma);
-void gtk_preview_set_color_cube (guint nred_shades,
- guint ngreen_shades,
- guint nblue_shades,
- guint ngray_shades);
-void gtk_preview_set_install_cmap (gint install_cmap);
-void gtk_preview_set_reserved (gint nreserved);
-GdkVisual* gtk_preview_get_visual (void);
-GdkColormap* gtk_preview_get_cmap (void);
-GtkPreviewInfo* gtk_preview_get_info (void);
+ selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
+ gtk_container_add (GTK_CONTAINER (window), selection_button);
+ gtk_widget_show (selection_button);
-That's all, folks!
+ gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
+ GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
+ gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
+ GTK_SIGNAL_FUNC (selection_clear), &have_selection);
+
+ gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING,
+ selection_handle, NULL);
+ gtk_widget_show (selection_button);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
</verb></tscreen>
+
<!-- ***************************************************************** -->
-<sect>The EventBox Widget<label id="sec_The_EventBox_Widget">
+<sect>glib<label id="sec_glib">
<!-- ***************************************************************** -->
-<p>
-Some gtk widgets don't have associated X windows, so they just draw on
-their parents. Because of this, they cannot recieve events
-and if they are incorrectly sized, they don't clip so you can get
-messy overwritting etc. If you require more from these widgets, the
-EventBox is for you.
-
-At first glance, the EventBox widget might appear to be totally
-useless. It draws nothing on the screen and responds to no
-events. However, it does serve a function - it provides an X window for
-its child widget. This is important as many GTK widgets do not
-have an associated X window. Not having an X window saves memory and
-improves performance, but also has some drawbacks. A widget without an
-X window cannot receive events, and does not perform any clipping on
-it's contents. Although the name <em/EventBox/ emphasizes the
-event-handling function, the widget can also be used for clipping.
-(And more ... see the example below.)
+<p>
+glib provides many useful functions and definitions available for use
+when creating GDK and GTK applications. I will list them all here with
+a brief explanation. Many are duplicates of standard libc functions so
+I won't go into detail on those. This is mostly to be used as a reference,
+so you know what is available for use.
-To create a new EventBox widget, use:
+<!-- ----------------------------------------------------------------- -->
+<sect1>Definitions
+<p>
+Definitions for the extremes of many of the standard types are:
<tscreen><verb>
-GtkWidget *gtk_event_box_new( void );
+G_MINFLOAT
+G_MAXFLOAT
+G_MINDOUBLE
+G_MAXDOUBLE
+G_MINSHORT
+G_MAXSHORT
+G_MININT
+G_MAXINT
+G_MINLONG
+G_MAXLONG
</verb></tscreen>
-A child widget can then be added to this EventBox:
+Also, the following typedefs. The ones left unspecified are dynamically set
+depending on the architecture. Remember to avoid counting on the size of a
+pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4
+on Intel.
<tscreen><verb>
-gtk_container_add( GTK_CONTAINER(event_box), widget );
-</verb></tscreen>
+char gchar;
+short gshort;
+long glong;
+int gint;
+char gboolean;
-The following example demonstrates both uses of an EventBox - a label
-is created that is clipped to a small box, and set up so that a
-mouse-click on the label causes the program to exit.
+unsigned char guchar;
+unsigned short gushort;
+unsigned long gulong;
+unsigned int guint;
-<tscreen><verb>
-/* example-start eventbox eventbox.c */
+float gfloat;
+double gdouble;
+long double gldouble;
-#include <gtk/gtk.h>
+void* gpointer;
-int
-main (int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *event_box;
- GtkWidget *label;
-
- gtk_init (&argc, &argv);
-
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
-
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- gtk_container_border_width (GTK_CONTAINER (window), 10);
-
- /* Create an EventBox and add it to our toplevel window */
-
- event_box = gtk_event_box_new ();
- gtk_container_add (GTK_CONTAINER(window), event_box);
- gtk_widget_show (event_box);
-
- /* Create a long label */
-
- label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
- gtk_container_add (GTK_CONTAINER (event_box), label);
- gtk_widget_show (label);
-
- /* Clip it short. */
- gtk_widget_set_usize (label, 110, 20);
-
- /* And bind an action to it */
- gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
- gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- /* Yet one more thing you need an X window for ... */
-
- gtk_widget_realize (event_box);
- gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
-
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+gint8
+guint8
+gint16
+guint16
+gint32
+guint32
</verb></tscreen>
-<!-- ***************************************************************** -->
-<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes">
-<!-- ***************************************************************** -->
+<!-- ----------------------------------------------------------------- -->
+<sect1>Doubly Linked Lists
<p>
-This describes the functions used to operate on widgets. These can be used
-to set style, padding, size etc.
-
-(Maybe I should make a whole section on accelerators.)
+The following functions are used to create, manage, and destroy doubly
+linked lists. I assume you know what linked lists are, as it is beyond the scope
+of this document to explain them. Of course, it's not required that you
+know these for general use of GTK, but they are nice to know.
<tscreen><verb>
-void gtk_widget_install_accelerator( GtkWidget *widget,
- GtkAcceleratorTable *table,
- gchar *signal_name,
- gchar key,
- guint8 modifiers );
+GList *g_list_alloc( void );
-void gtk_widget_remove_accelerator ( GtkWidget *widget,
- GtkAcceleratorTable *table,
- gchar *signal_name);
+void g_list_free( GList *list );
-void gtk_widget_activate( GtkWidget *widget );
+void g_list_free_1( GList *list );
-void gtk_widget_set_name( GtkWidget *widget,
- gchar *name );
+GList *g_list_append( GList *list,
+ gpointer data );
+
+GList *g_list_prepend( GList *list,
+ gpointer data );
+
+GList *g_list_insert( GList *list,
+ gpointer data,
+ gint position );
-gchar *gtk_widget_get_name( GtkWidget *widget );
+GList *g_list_remove( GList *list,
+ gpointer data );
+
+GList *g_list_remove_link( GList *list,
+ GList *link );
-void gtk_widget_set_sensitive( GtkWidget *widget,
- gint sensitive );
+GList *g_list_reverse( GList *list );
+
+GList *g_list_nth( GList *list,
+ gint n );
+
+GList *g_list_find( GList *list,
+ gpointer data );
+
+GList *g_list_last( GList *list );
+
+GList *g_list_first( GList *list );
+
+gint g_list_length( GList *list );
+
+void g_list_foreach( GList *list,
+ GFunc func,
+ gpointer user_data );
+</verb></tscreen>
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Singly Linked Lists
+<p>
+Many of the above functions for singly linked lists are identical to the
+above. Here is a complete list:
+<tscreen><verb>
+GSList *g_slist_alloc( void );
+
+void g_slist_free( GSList *list );
+
+void g_slist_free_1( GSList *list );
+
+GSList *g_slist_append( GSList *list,
+ gpointer data );
+
+GSList *g_slist_prepend( GSList *list,
+ gpointer data );
+
+GSList *g_slist_insert( GSList *list,
+ gpointer data,
+ gint position );
+
+GSList *g_slist_remove( GSList *list,
+ gpointer data );
+
+GSList *g_slist_remove_link( GSList *list,
+ GSList *link );
+
+GSList *g_slist_reverse( GSList *list );
+
+GSList *g_slist_nth( GSList *list,
+ gint n );
+
+GSList *g_slist_find( GSList *list,
+ gpointer data );
+
+GSList *g_slist_last( GSList *list );
-void gtk_widget_set_style( GtkWidget *widget,
- GtkStyle *style );
-
-GtkStyle *gtk_widget_get_style( GtkWidget *widget );
+gint g_slist_length( GSList *list );
-GtkStyle *gtk_widget_get_default_style( void );
+void g_slist_foreach( GSList *list,
+ GFunc func,
+ gpointer user_data );
+
+</verb></tscreen>
-void gtk_widget_set_uposition( GtkWidget *widget,
- gint x,
- gint y );
+<!-- ----------------------------------------------------------------- -->
+<sect1>Memory Management
+<p>
+<tscreen><verb>
+gpointer g_malloc( gulong size );
+</verb></tscreen>
-void gtk_widget_set_usize( GtkWidget *widget,
- gint width,
- gint height );
+This is a replacement for malloc(). You do not need to check the return
+vaule as it is done for you in this function.
-void gtk_widget_grab_focus( GtkWidget *widget );
+<tscreen><verb>
+gpointer g_malloc0( gulong size );
+</verb></tscreen>
-void gtk_widget_show( GtkWidget *widget );
+Same as above, but zeroes the memory before returning a pointer to it.
-void gtk_widget_hide( GtkWidget *widget );
+<tscreen><verb>
+gpointer g_realloc( gpointer mem,
+ gulong size );
</verb></tscreen>
-<!-- ***************************************************************** -->
-<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts">
-<!-- ***************************************************************** -->
-
-<!-- ----------------------------------------------------------------- -->
-<sect1>Timeouts
-<p>
-You may be wondering how you make GTK do useful work when in gtk_main.
-Well, you have several options. Using the following functions you can
-create a timeout function that will be called every "interval"
-milliseconds.
+Relocates "size" bytes of memory starting at "mem". Obviously, the
+memory should have been previously allocated.
<tscreen><verb>
-gint gtk_timeout_add( guint32 interval,
- GtkFunction function,
- gpointer data );
+void g_free( gpointer mem );
</verb></tscreen>
-The first argument is the number of milliseconds between calls to your
-function. The second argument is the function you wish to have called, and
-the third, the data passed to this callback function. The return value is
-an integer "tag" which may be used to stop the timeout by calling:
+Frees memory. Easy one.
<tscreen><verb>
-void gtk_timeout_remove( gint tag );
+void g_mem_profile( void );
</verb></tscreen>
-You may also stop the timeout function by returning zero or FALSE from
-your callback function. Obviously this means if you want your function to
-continue to be called, it should return a non-zero value, ie TRUE.
-
-The declaration of your callback should look something like this:
+Dumps a profile of used memory, but requries that you add #define
+MEM_PROFILE to the top of glib/gmem.c and re-make and make install.
<tscreen><verb>
-gint timeout_callback( gpointer data );
+void g_mem_check( gpointer mem );
</verb></tscreen>
+Checks that a memory location is valid. Requires you add #define
+MEM_CHECK to the top of gmem.c and re-make and make install.
+
<!-- ----------------------------------------------------------------- -->
-<sect1>Monitoring IO
+<sect1>Timers
<p>
-Another nifty feature of GTK, is the ability to have it check for data on a
-file descriptor for you (as returned by open(2) or socket(2)). This is
-especially useful for networking applications. The function:
+Timer functions..
<tscreen><verb>
-gint gdk_input_add( gint source,
- GdkInputCondition condition,
- GdkInputFunction function,
- gpointer data );
-</verb></tscreen>
-
-Where the first argument is the file descriptor you wish to have watched,
-and the second specifies what you want GDK to look for. This may be one of:
+GTimer *g_timer_new( void );
-<itemize>
-<item>GDK_INPUT_READ - Call your function when there is data ready for
-reading on your file descriptor.
+void g_timer_destroy( GTimer *timer );
-<item>GDK_INPUT_WRITE - Call your function when the file descriptor is
-ready for writing.
-</itemize>
+void g_timer_start( GTimer *timer );
-As I'm sure you've figured out already, the third argument is the function
-you wish to have called when the above conditions are satisfied, and the
-fourth is the data to pass to this function.
+void g_timer_stop( GTimer *timer );
-The return value is a tag that may be used to stop GDK from monitoring this
-file descriptor using the following function.
+void g_timer_reset( GTimer *timer );
-<tscreen><verb>
-void gdk_input_remove( gint tag );
-</verb></tscreen>
+gdouble g_timer_elapsed( GTimer *timer,
+ gulong *microseconds );
+</verb></tscreen>
-The callback function should be declared as:
+<!-- ----------------------------------------------------------------- -->
+<sect1>String Handling
+<p>
+A whole mess of string handling functions. They all look very interesting, and
+probably better for many purposes than the standard C string functions, but
+require documentation.
<tscreen><verb>
-void input_callback( gpointer data,
- gint source,
- GdkInputCondition condition );
-</verb></tscreen>
+GString *g_string_new( gchar *init );
-Where <tt/source/ and <tt/condition/ are as specified above.
+void g_string_free( GString *string,
+ gint free_segment );
+
+GString *g_string_assign( GString *lval,
+ gchar *rval );
+
+GString *g_string_truncate( GString *string,
+ gint len );
+
+GString *g_string_append( GString *string,
+ gchar *val );
+
+GString *g_string_append_c( GString *string,
+ gchar c );
+
+GString *g_string_prepend( GString *string,
+ gchar *val );
+
+GString *g_string_prepend_c( GString *string,
+ gchar c );
+
+void g_string_sprintf( GString *string,
+ gchar *fmt,
+ ...);
+
+void g_string_sprintfa ( GString *string,
+ gchar *fmt,
+ ... );
+</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect1>Idle Functions
+<sect1>Utility and Error Functions
<p>
-<!-- Need to check on idle priorities - TRG -->
-What if you have a function you want called when nothing else is
-happening ?
-
<tscreen><verb>
-gint gtk_idle_add( GtkFunction function,
- gpointer data );
+gchar *g_strdup( const gchar *str );
</verb></tscreen>
-This causes GTK to call the specified function whenever nothing else is
-happening.
+Replacement strdup function. Copies the original strings contents to
+newly allocated memory, and returns a pointer to it.
<tscreen><verb>
-void gtk_idle_remove( gint tag );
+gchar *g_strerror( gint errnum );
</verb></tscreen>
-I won't explain the meaning of the arguments as they follow very much like
-the ones above. The function pointed to by the first argument to
-gtk_idle_add will be called whenever the opportunity arises. As with the
-others, returning FALSE will stop the idle function from being called.
+I recommend using this for all error messages. It's much nicer, and more
+portable than perror() or others. The output is usually of the form:
-<!-- ***************************************************************** -->
-<sect>Managing Selections
-<!-- ***************************************************************** -->
+<tscreen><verb>
+program name:function that failed:file or further description:strerror
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
-<p>
-One type of interprocess communication supported by GTK is
-<em>selections</em>. A selection identifies a chunk of data, for
-instance, a portion of text, selected by the user in some fashion, for
-instance, by dragging with the mouse. Only one application on a
-display, (the <em>owner</em> can own a particular selection at one
-time, so when a selection is claimed by one application, the previous
-owner must indicate to the user that selection has been
-relinquished. Other applications can request the contents of a
-selection in different forms, called <em>targets</em>. There can be
-any number of selections, but most X applications only handle one, the
-<em>primary selection</em>.
+Here's an example of one such call used in our hello_world program:
-In most cases, it isn't necessary for a GTK application to deal with
-selections itself. The standard widgets, such as the Entry widget,
-already have the capability to claim the selection when appropriate
-(e.g., when the user drags over text), and to retrieve the contents of
-the selection owned by another widget, or another application (e.g.,
-when the user clicks the second mouse button). However, there may be
-cases in which you want to give other widgets the ability to supply
-the selection, or you wish to retrieve targets not supported by
-default.
+<tscreen><verb>
+g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
+</verb></tscreen>
-A fundamental concept needed to understand selection handling is that
-of the <em>atom</em>. An atom is an integer that uniquely identifies a
-string (on a certain display). Certain atoms are predefined by the X
-server, and in some cases there are constants in <tt>gtk.h</tt>
-corresponding to these atoms. For instance the constant
-<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY".
-In other cases, you should use the functions
-<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string,
-and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both
-selections and targets are identifed by atoms.
+<tscreen><verb>
+void g_error( gchar *format, ... );
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Retrieving the selection
-<p>
-Retrieving the selection is an asynchronous process. To start the
-process, you call:
+Prints an error message. The format is just like printf, but it
+prepends "** ERROR **: " to your message, and exits the program.
+Use only for fatal errors.
<tscreen><verb>
-gint gtk_selection_convert( GtkWidget *widget,
- GdkAtom selection,
- GdkAtom target,
- guint32 time );
-</verb</tscreen>
-
-This <em>converts</em> the selection into the form specified by
-<tt/target/. If at all possible, the time field should be the time
-from the event that triggered the selection. This helps make sure that
-events occur in the order that the user requested them. However, if it
-is not available (for instance, if the conversion was triggered by
-a "clicked" signal), then you can use the constant
-<tt>GDK_CURRENT_TIME</tt>.
+void g_warning( gchar *format, ... );
+</verb></tscreen>
-When the selection owner responds to the request, a
-"selection_received" signal is sent to your application. The handler
-for this signal receives a pointer to a <tt>GtkSelectionData</tt>
-structure, which is defined as:
+Same as above, but prepends "** WARNING **: ", and does not exit the
+program.
<tscreen><verb>
-struct _GtkSelectionData
-{
- GdkAtom selection;
- GdkAtom target;
- GdkAtom type;
- gint format;
- guchar *data;
- gint length;
-};
+void g_message( gchar *format, ... );
+</verb></tscreen>
+
+Prints "message: " prepended to the string you pass in.
+
+<tscreen><verb>
+void g_print( gchar *format, ... );
</verb></tscreen>
-<tt>selection</tt> and <tt>target</tt> are the values you gave in your
-<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that
-identifies the type of data returned by the selection owner. Some
-possible values are "STRING", a string of latin-1 characters, "ATOM",
-a series of atoms, "INTEGER", an integer, etc. Most targets can only
-return one type. <tt/format/ gives the length of the units (for
-instance characters) in bits. Usually, you don't care about this when
-receiving data. <tt>data</tt> is a pointer to the returned data, and
-<tt>length</tt> gives the length of the returned data, in bytes. If
-<tt>length</tt> is negative, then an error occurred and the selection
-could not be retrieved. This might happen if no application owned the
-selection, or if you requested a target that the application didn't
-support. The buffer is actually guaranteed to be one byte longer than
-<tt>length</tt>; the extra byte will always be zero, so it isn't
-necessary to make a copy of strings just to null terminate them.
+Replacement for printf().
-In the following example, we retrieve the special target "TARGETS",
-which is a list of all targets into which the selection can be
-converted.
+And our last function:
<tscreen><verb>
-/* example-start selection gettargets.c */
-
-#include <gtk/gtk.h>
+gchar *g_strsignal( gint signum );
+</verb></tscreen>
-void selection_received (GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data);
+Prints out the name of the Unix system signal given the signal number.
+Useful in generic signal handling functions.
-/* Signal handler invoked when user clicks on the "Get Targets" button */
-void
-get_targets (GtkWidget *widget, gpointer data)
-{
- static GdkAtom targets_atom = GDK_NONE;
+All of the above are more or less just stolen from glib.h. If anyone cares
+to document any function, just send me an email!
- /* Get the atom corresonding to the string "TARGETS" */
- if (targets_atom == GDK_NONE)
- targets_atom = gdk_atom_intern ("TARGETS", FALSE);
+<!-- ***************************************************************** -->
+<sect>GTK's rc Files
+<!-- ***************************************************************** -->
+<p>
+GTK has it's own way of dealing with application defaults, by using rc
+files. These can be used to set the colors of just about any widget, and
+can also be used to tile pixmaps onto the background of some widgets.
- /* And request the "TARGETS" target for the primary selection */
- gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
- GDK_CURRENT_TIME);
-}
+<!-- ----------------------------------------------------------------- -->
+<sect1>Functions For rc Files
+<p>
+When your application starts, you should include a call to:
-/* Signal handler called when the selections owner returns the data */
-void
-selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
- gpointer data)
-{
- GdkAtom *atoms;
- GList *item_list;
- int i;
+<tscreen><verb>
+void gtk_rc_parse( char *filename );
+</verb></tscreen>
- /* **** IMPORTANT **** Check to see if retrieval succeeded */
- if (selection_data->length < 0)
- {
- g_print ("Selection retrieval failed\n");
- return;
- }
- /* Make sure we got the data in the expected form */
- if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
- {
- g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
- return;
- }
-
- /* Print out the atoms we received */
- atoms = (GdkAtom *)selection_data->data;
+Passing in the filename of your rc file. This will cause GTK to parse this
+file, and use the style settings for the widget types defined there.
- item_list = NULL;
- for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
- {
- char *name;
- name = gdk_atom_name (atoms[i]);
- if (name != NULL)
- g_print ("%s\n",name);
- else
- g_print ("(bad atom)\n");
- }
+If you wish to have a special set of widgets that can take on a different
+style from others, or any other logical division of widgets, use a call to:
- return;
-}
+<tscreen><verb>
+void gtk_widget_set_name( GtkWidget *widget,
+ gchar *name );
+</verb></tscreen>
-int
-main (int argc, char *argv[])
-{
- GtkWidget *window;
- GtkWidget *button;
-
- gtk_init (&argc, &argv);
+Passing your newly created widget as the first argument, and the name
+you wish to give it as the second. This will allow you to change the
+attributes of this widget by name through the rc file.
- /* Create the toplevel window */
+If we use a call something like this:
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+<tscreen><verb>
+button = gtk_button_new_with_label ("Special Button");
+gtk_widget_set_name (button, "special button");
+</verb></tscreen>
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
+Then this button is given the name "special button" and may be addressed by
+name in the rc file as "special button.GtkButton". [<--- Verify ME!]
- /* Create a button the user can click to get targets */
+The example rc file below, sets the properties of the main window, and lets
+all children of that main window inherit the style described by the "main
+button" style. The code used in the application is:
- button = gtk_button_new_with_label ("Get Targets");
- gtk_container_add (GTK_CONTAINER (window), button);
+<tscreen><verb>
+window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+gtk_widget_set_name (window, "main window");
+</verb></tscreen>
- gtk_signal_connect (GTK_OBJECT(button), "clicked",
- GTK_SIGNAL_FUNC (get_targets), NULL);
- gtk_signal_connect (GTK_OBJECT(button), "selection_received",
- GTK_SIGNAL_FUNC (selection_received), NULL);
+And then the style is defined in the rc file using:
- gtk_widget_show (button);
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+<tscreen><verb>
+widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
+Which sets all the GtkButton widgets in the "main window" to the
+"main_buttons" style as defined in the rc file.
+
+As you can see, this is a fairly powerful and flexible system. Use your
+imagination as to how best to take advantage of this.
+
<!-- ----------------------------------------------------------------- -->
-<sect1> Supplying the selection
+<sect1>GTK's rc File Format
<p>
-Supplying the selection is a bit more complicated. You must register
-handlers that will be called when your selection is requested. For
-each selection/target pair you will handle, you make a call to:
+The format of the GTK file is illustrated in the example below. This is
+the testgtkrc file from the GTK distribution, but I've added a
+few comments and things. You may wish to include this explanation
+your application to allow the user to fine tune his application.
-<tscreen><verb>
-void gtk_selection_add_handler( GtkWidget *widget,
- GdkAtom selection,
- GdkAtom target,
- GtkSelectionFunction function,
- GtkRemoveFunction remove_func,
- gpointer data );
-</verb></tscreen>
+There are several directives to change the attributes of a widget.
-<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
-this handler will manage. <tt/remove_func/, if not
-NULL, will be called when the signal handler is removed. This is
-useful, for instance, for interpreted languages which need to
-keep track of a reference count for <tt/data/.
+<itemize>
+<item>fg - Sets the foreground color of a widget.
+<item>bg - Sets the background color of a widget.
+<item>bg_pixmap - Sets the background of a widget to a tiled pixmap.
+<item>font - Sets the font to be used with the given widget.
+</itemize>
-The callback function has the signature:
+In addition to this, there are several states a widget can be in, and you
+can set different colors, pixmaps and fonts for each state. These states are:
-<tscreen><verb>
-typedef void (*GtkSelectionFunction)( GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data );
+<itemize>
+<item>NORMAL - The normal state of a widget, without the mouse over top of
+it, and not being pressed etc.
+<item>PRELIGHT - When the mouse is over top of the widget, colors defined
+using this state will be in effect.
+<item>ACTIVE - When the widget is pressed or clicked it will be active, and
+the attributes assigned by this tag will be in effect.
+<item>INSENSITIVE - When a widget is set insensitive, and cannot be
+activated, it will take these attributes.
+<item>SELECTED - When an object is selected, it takes these attributes.
+</itemize>
+
+When using the "fg" and "bg" keywords to set the colors of widgets, the
+format is:
+<tscreen><verb>
+fg[<STATE>] = { Red, Green, Blue }
</verb></tscreen>
-The GtkSelectionData is the same as above, but this time, we're
-responsible for filling in the fields <tt/type/, <tt/format/,
-<tt/data/, and <tt/length/. (The <tt/format/ field is actually
-important here - the X server uses it to figure out whether the data
-needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a
-character - or 32 - <em/i.e./ a. integer.) This is done by calling the
-function:
+Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red,
+Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being
+white. They must be in float form, or they will register as 0, so a straight
+"1" will not work, it must be "1.0". A straight "0" is fine because it
+doesn't matter if it's not recognized. Unrecognized values are set to 0.
+bg_pixmap is very similar to the above, except the colors are replaced by a
+filename.
+
+pixmap_path is a list of paths seperated by ":"'s. These paths will be
+searched for any pixmap you specify.
+
+The font directive is simply:
<tscreen><verb>
-void gtk_selection_data_set( GtkSelectionData *selection_data,
- GdkAtom type,
- gint format,
- guchar *data,
- gint length );
+font = "<font name>"
</verb></tscreen>
-This function takes care of properly making a copy of the data so that
-you don't have to worry about keeping it around. (You should not fill
-in the fields of the GtkSelectionData structure by hand.)
+Where the only hard part is figuring out the font string. Using xfontsel or
+similar utility should help.
+
+The "widget_class" sets the style of a class of widgets. These classes are
+listed in the widget overview on the class hierarchy.
+
+The "widget" directive sets a specificaly named set of widgets to a
+given style, overriding any style set for the given widget class.
+These widgets are registered inside the application using the
+gtk_widget_set_name() call. This allows you to specify the attributes of a
+widget on a per widget basis, rather than setting the attributes of an
+entire widget class. I urge you to document any of these special widgets so
+users may customize them.
+
+When the keyword <tt>parent</> is used as an attribute, the widget will take on
+the attributes of it's parent in the application.
-When prompted by the user, you claim ownership of the selection by
-calling:
+When defining a style, you may assign the attributes of a previously defined
+style to this new one.
<tscreen><verb>
-gint gtk_selection_owner_set( GtkWidget *widget,
- GdkAtom selection,
- guint32 time );
+style "main_button" = "button"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
+ bg[PRELIGHT] = { 0.75, 0, 0 }
+}
</verb></tscreen>
-If another application claims ownership of the selection, you will
-receive a "selection_clear_event".
+This example takes the "button" style, and creates a new "main_button" style
+simply by changing the font and prelight background color of the "button"
+style.
-As an example of supplying the selection, the following program adds
-selection functionality to a toggle button. When the toggle button is
-depressed, the program claims the primary selection. The only target
-supported (aside from certain targets like "TARGETS" supplied by GTK
-itself), is the "STRING" target. When this target is requested, a
-string representation of the time is returned.
+Of course, many of these attributes don't apply to all widgets. It's a
+simple matter of common sense really. Anything that could apply, should.
+
+<!-- ----------------------------------------------------------------- -->
+<sect1>Example rc file
+<p>
<tscreen><verb>
-/* example-start selection setselection.c */
+# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
+#
+pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
+#
+# style <name> [= <name>]
+# {
+# <option>
+# }
+#
+# widget <widget_set> style <style_name>
+# widget_class <widget_class_set> style <style_name>
-#include <gtk/gtk.h>
-#include <time.h>
-/* Callback when the user toggles the selection */
-void
-selection_toggled (GtkWidget *widget, gint *have_selection)
+# Here is a list of all the possible states. Note that some do not apply to
+# certain widgets.
+#
+# NORMAL - The normal state of a widget, without the mouse over top of
+# it, and not being pressed etc.
+#
+# PRELIGHT - When the mouse is over top of the widget, colors defined
+# using this state will be in effect.
+#
+# ACTIVE - When the widget is pressed or clicked it will be active, and
+# the attributes assigned by this tag will be in effect.
+#
+# INSENSITIVE - When a widget is set insensitive, and cannot be
+# activated, it will take these attributes.
+#
+# SELECTED - When an object is selected, it takes these attributes.
+#
+# Given these states, we can set the attributes of the widgets in each of
+# these states using the following directives.
+#
+# fg - Sets the foreground color of a widget.
+# fg - Sets the background color of a widget.
+# bg_pixmap - Sets the background of a widget to a tiled pixmap.
+# font - Sets the font to be used with the given widget.
+#
+
+# This sets a style called "button". The name is not really important, as
+# it is assigned to the actual widgets at the bottom of the file.
+
+style "window"
{
- if (GTK_TOGGLE_BUTTON(widget)->active)
- {
- *have_selection = gtk_selection_owner_set (widget,
- GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME);
- /* if claiming the selection failed, we return the button to
- the out state */
- if (!*have_selection)
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
- }
- else
- {
- if (*have_selection)
- {
- /* Before clearing the selection by setting the owner to NULL,
- we check if we are the actual owner */
- if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
- gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME);
- *have_selection = FALSE;
- }
- }
+ #This sets the padding around the window to the pixmap specified.
+ #bg_pixmap[<STATE>] = "<pixmap filename>"
+ bg_pixmap[NORMAL] = "warning.xpm"
}
-/* Called when another application claims the selection */
-gint
-selection_clear (GtkWidget *widget, GdkEventSelection *event,
- gint *have_selection)
+style "scale"
{
- *have_selection = FALSE;
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
-
- return TRUE;
+ #Sets the foreground color (font color) to red when in the "NORMAL"
+ #state.
+
+ fg[NORMAL] = { 1.0, 0, 0 }
+
+ #Sets the background pixmap of this widget to that of it's parent.
+ bg_pixmap[NORMAL] = "<parent>"
}
-/* Supplies the current time as the selection. */
-void
-selection_handle (GtkWidget *widget,
- GtkSelectionData *selection_data,
- gpointer data)
+style "button"
{
- gchar *timestr;
- time_t current_time;
+ # This shows all the possible states for a button. The only one that
+ # doesn't apply is the SELECTED state.
+
+ fg[PRELIGHT] = { 0, 1.0, 1.0 }
+ bg[PRELIGHT] = { 0, 0, 1.0 }
+ bg[ACTIVE] = { 1.0, 0, 0 }
+ fg[ACTIVE] = { 0, 1.0, 0 }
+ bg[NORMAL] = { 1.0, 1.0, 0 }
+ fg[NORMAL] = { .99, 0, .99 }
+ bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
+ fg[INSENSITIVE] = { 1.0, 0, 1.0 }
+}
- current_time = time (NULL);
- timestr = asctime (localtime(&current_time));
- /* When we return a single string, it should not be null terminated.
- That will be done for us */
+# In this example, we inherit the attributes of the "button" style and then
+# override the font and background color when prelit to create a new
+# "main_button" style.
- gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
- 8, timestr, strlen(timestr));
+style "main_button" = "button"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
+ bg[PRELIGHT] = { 0.75, 0, 0 }
}
-int
-main (int argc, char *argv[])
+style "toggle_button" = "button"
{
- GtkWidget *window;
-
- GtkWidget *selection_button;
-
- static int have_selection = FALSE;
+ fg[NORMAL] = { 1.0, 0, 0 }
+ fg[ACTIVE] = { 1.0, 0, 0 }
- gtk_init (&argc, &argv);
-
- /* Create the toplevel window */
-
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title (GTK_WINDOW (window), "Event Box");
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+ # This sets the background pixmap of the toggle_button to that of it's
+ # parent widget (as defined in the application).
+ bg_pixmap[NORMAL] = "<parent>"
+}
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
+style "text"
+{
+ bg_pixmap[NORMAL] = "marble.xpm"
+ fg[NORMAL] = { 1.0, 1.0, 1.0 }
+}
- /* Create a toggle button to act as the selection */
+style "ruler"
+{
+ font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
+}
- selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
- gtk_container_add (GTK_CONTAINER (window), selection_button);
- gtk_widget_show (selection_button);
+# pixmap_path "~/.pixmaps"
- gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
- GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
- gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
- GTK_SIGNAL_FUNC (selection_clear), &have_selection);
+# These set the widget types to use the styles defined above.
+# The widget types are listed in the class hierarchy, but could probably be
+# just listed in this document for the users reference.
- gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
- GDK_SELECTION_TYPE_STRING,
- selection_handle, NULL);
+widget_class "GtkWindow" style "window"
+widget_class "GtkDialog" style "window"
+widget_class "GtkFileSelection" style "window"
+widget_class "*Gtk*Scale" style "scale"
+widget_class "*GtkCheckButton*" style "toggle_button"
+widget_class "*GtkRadioButton*" style "toggle_button"
+widget_class "*GtkButton*" style "button"
+widget_class "*Ruler" style "ruler"
+widget_class "*GtkText" style "text"
- gtk_widget_show (selection_button);
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
-/* example-end */
+# This sets all the buttons that are children of the "main window" to
+# the main_buton style. These must be documented to be taken advantage of.
+widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
-
<!-- ***************************************************************** -->
-<sect>glib<label id="sec_glib">
+<sect>Writing Your Own Widgets
<!-- ***************************************************************** -->
+
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
<p>
-glib provides many useful functions and definitions available for use
-when creating GDK and GTK applications. I will list them all here with
-a brief explanation. Many are duplicates of standard libc functions so
-I won't go into detail on those. This is mostly to be used as a reference,
-so you know what is available for use.
+Although the GTK distribution comes with many types of widgets that
+should cover most basic needs, there may come a time when you need to
+create your own new widget type. Since GTK uses widget inheretence
+extensively, and there is already a widget that is close to what you want,
+it is often possible to make a useful new widget type in
+just a few lines of code. But before starting work on a new widget, check
+around first to make sure that someone has not already written
+it. This will prevent duplication of effort and keep the number of
+GTK widgets out there to a minimum, which will help keep both the code
+and the interface of different applications consistent. As a flip side
+to this, once you finish your widget, announce it to the world so
+other people can benefit. The best place to do this is probably the
+<tt>gtk-list</tt>.
+
+Complete sources for the example widgets are available at the place you
+got this tutorial, or from:
+
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+
<!-- ----------------------------------------------------------------- -->
-<sect1>Definitions
+<sect1> The Anatomy Of A Widget
<p>
-Definitions for the extremes of many of the standard types are:
+In order to create a new widget, it is important to have an
+understanding of how GTK objects work. This section is just meant as a
+brief overview. See the reference documentation for the details.
+
+GTK widgets are implemented in an object oriented fashion. However,
+they are implemented in standard C. This greatly improves portability
+and stability over using current generation C++ compilers; however,
+it does mean that the widget writer has to pay attention to some of
+the implementation details. The information common to all instances of
+one class of widgets (e.g., to all Button widgets) is stored in the
+<em>class structure</em>. There is only one copy of this in
+which is stored information about the class's signals
+(which act like virtual functions in C). To support inheritance, the
+first field in the class structure must be a copy of the parent's
+class structure. The declaration of the class structure of GtkButtton
+looks like:
<tscreen><verb>
-G_MINFLOAT
-G_MAXFLOAT
-G_MINDOUBLE
-G_MAXDOUBLE
-G_MINSHORT
-G_MAXSHORT
-G_MININT
-G_MAXINT
-G_MINLONG
-G_MAXLONG
+struct _GtkButtonClass
+{
+ GtkContainerClass parent_class;
+
+ void (* pressed) (GtkButton *button);
+ void (* released) (GtkButton *button);
+ void (* clicked) (GtkButton *button);
+ void (* enter) (GtkButton *button);
+ void (* leave) (GtkButton *button);
+};
</verb></tscreen>
-Also, the following typedefs. The ones left unspecified are dynamically set
-depending on the architecture. Remember to avoid counting on the size of a
-pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4
-on Intel.
+When a button is treated as a container (for instance, when it is
+resized), its class structure can be cast to GtkContainerClass, and
+the relevant fields used to handle the signals.
+
+There is also a structure for each widget that is created on a
+per-instance basis. This structure has fields to store information that
+is different for each instance of the widget. We'll call this
+structure the <em>object structure</em>. For the Button class, it looks
+like:
<tscreen><verb>
-char gchar;
-short gshort;
-long glong;
-int gint;
-char gboolean;
+struct _GtkButton
+{
+ GtkContainer container;
-unsigned char guchar;
-unsigned short gushort;
-unsigned long gulong;
-unsigned int guint;
+ GtkWidget *child;
-float gfloat;
-double gdouble;
-long double gldouble;
+ guint in_button : 1;
+ guint button_down : 1;
+};
+</verb></tscreen>
-void* gpointer;
+Note that, similar to the class structure, the first field is the
+object structure of the parent class, so that this structure can be
+cast to the parent class's object structure as needed.
-gint8
-guint8
-gint16
-guint16
-gint32
-guint32
-</verb></tscreen>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Creating a Composite widget
<!-- ----------------------------------------------------------------- -->
-<sect1>Doubly Linked Lists
+<sect2> Introduction
<p>
-The following functions are used to create, manage, and destroy doubly
-linked lists. I assume you know what linked lists are, as it is beyond the scope
-of this document to explain them. Of course, it's not required that you
-know these for general use of GTK, but they are nice to know.
-
-<tscreen><verb>
-GList *g_list_alloc( void );
+One type of widget that you may be interested in creating is a
+widget that is merely an aggregate of other GTK widgets. This type of
+widget does nothing that couldn't be done without creating new
+widgets, but provides a convenient way of packaging user interface
+elements for reuse. The FileSelection and ColorSelection widgets in
+the standard distribution are examples of this type of widget.
-void g_list_free( GList *list );
+The example widget that we'll create in this section is the Tictactoe
+widget, a 3x3 array of toggle buttons which triggers a signal when all
+three buttons in a row, column, or on one of the diagonals are
+depressed.
-void g_list_free_1( GList *list );
+<!-- ----------------------------------------------------------------- -->
+<sect2> Choosing a parent class
+<p>
+The parent class for a composite widget is typically the container
+class that holds all of the elements of the composite widget. For
+example, the parent class of the FileSelection widget is the
+Dialog class. Since our buttons will be arranged in a table, it
+might seem natural to make our parent class the GtkTable
+class. Unfortunately, this turns out not to work. The creation of a
+widget is divided among two functions - a <tt/WIDGETNAME_new()/
+function that the user calls, and a <tt/WIDGETNAME_init()/ function
+which does the basic work of initializing the widget which is
+independent of the arguments passed to the <tt/_new()/
+function. Descendent widgets only call the <tt/_init/ function of
+their parent widget. But this division of labor doesn't work well for
+tables, which when created, need to know the number of rows and
+columns in the table. Unless we want to duplicate most of the
+functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had
+best avoid deriving it from GtkTable. For that reason, we derive it
+from GtkVBox instead, and stick our table inside the VBox.
-GList *g_list_append( GList *list,
- gpointer data );
-
-GList *g_list_prepend( GList *list,
- gpointer data );
-
-GList *g_list_insert( GList *list,
- gpointer data,
- gint position );
+<!-- ----------------------------------------------------------------- -->
+<sect2> The header file
+<p>
+Each widget class has a header file which declares the object and
+class structures for that widget, along with public functions.
+A couple of features are worth pointing out. To prevent duplicate
+definitions, we wrap the entire header file in:
-GList *g_list_remove( GList *list,
- gpointer data );
-
-GList *g_list_remove_link( GList *list,
- GList *link );
+<tscreen><verb>
+#ifndef __TICTACTOE_H__
+#define __TICTACTOE_H__
+.
+.
+.
+#endif /* __TICTACTOE_H__ */
+</verb></tscreen>
-GList *g_list_reverse( GList *list );
+And to keep C++ programs that include the header file happy, in:
-GList *g_list_nth( GList *list,
- gint n );
-
-GList *g_list_find( GList *list,
- gpointer data );
+<tscreen><verb>
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+.
+.
+.
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+</verb></tscreen>
-GList *g_list_last( GList *list );
+Along with the functions and structures, we declare three standard
+macros in our header file, <tt/TICTACTOE(obj)/,
+<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a
+pointer into a pointer to the object or class structure, and check
+if an object is a Tictactoe widget respectively.
-GList *g_list_first( GList *list );
+Here is the complete header file:
-gint g_list_length( GList *list );
+<tscreen><verb>
+/* tictactoe.h */
-void g_list_foreach( GList *list,
- GFunc func,
- gpointer user_data );
-</verb></tscreen>
+#ifndef __TICTACTOE_H__
+#define __TICTACTOE_H__
-<!-- ----------------------------------------------------------------- -->
-<sect1>Singly Linked Lists
-<p>
-Many of the above functions for singly linked lists are identical to the
-above. Here is a complete list:
-<tscreen><verb>
-GSList *g_slist_alloc( void );
+#include <gdk/gdk.h>
+#include <gtk/gtkvbox.h>
-void g_slist_free( GSList *list );
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-void g_slist_free_1( GSList *list );
+#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
+#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
+#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
-GSList *g_slist_append( GSList *list,
- gpointer data );
-
-GSList *g_slist_prepend( GSList *list,
- gpointer data );
-
-GSList *g_slist_insert( GSList *list,
- gpointer data,
- gint position );
-
-GSList *g_slist_remove( GSList *list,
- gpointer data );
-
-GSList *g_slist_remove_link( GSList *list,
- GSList *link );
-
-GSList *g_slist_reverse( GSList *list );
-GSList *g_slist_nth( GSList *list,
- gint n );
-
-GSList *g_slist_find( GSList *list,
- gpointer data );
-
-GSList *g_slist_last( GSList *list );
+typedef struct _Tictactoe Tictactoe;
+typedef struct _TictactoeClass TictactoeClass;
-gint g_slist_length( GSList *list );
+struct _Tictactoe
+{
+ GtkVBox vbox;
+
+ GtkWidget *buttons[3][3];
+};
-void g_slist_foreach( GSList *list,
- GFunc func,
- gpointer user_data );
-
-</verb></tscreen>
+struct _TictactoeClass
+{
+ GtkVBoxClass parent_class;
-<!-- ----------------------------------------------------------------- -->
-<sect1>Memory Management
-<p>
-<tscreen><verb>
-gpointer g_malloc( gulong size );
-</verb></tscreen>
+ void (* tictactoe) (Tictactoe *ttt);
+};
-This is a replacement for malloc(). You do not need to check the return
-vaule as it is done for you in this function.
+guint tictactoe_get_type (void);
+GtkWidget* tictactoe_new (void);
+void tictactoe_clear (Tictactoe *ttt);
-<tscreen><verb>
-gpointer g_malloc0( gulong size );
-</verb></tscreen>
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-Same as above, but zeroes the memory before returning a pointer to it.
+#endif /* __TICTACTOE_H__ */
-<tscreen><verb>
-gpointer g_realloc( gpointer mem,
- gulong size );
</verb></tscreen>
-Relocates "size" bytes of memory starting at "mem". Obviously, the
-memory should have been previously allocated.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The <tt/_get_type()/ function.
+<p>
+We now continue on to the implementation of our widget. A core
+function for every widget is the function
+<tt/WIDGETNAME_get_type()/. This function, when first called, tells
+GTK about the widget class, and gets an ID that uniquely identifies
+the widget class. Upon subsequent calls, it just returns the ID.
<tscreen><verb>
-void g_free( gpointer mem );
-</verb></tscreen>
+guint
+tictactoe_get_type ()
+{
+ static guint ttt_type = 0;
-Frees memory. Easy one.
+ if (!ttt_type)
+ {
+ GtkTypeInfo ttt_info =
+ {
+ "Tictactoe",
+ sizeof (Tictactoe),
+ sizeof (TictactoeClass),
+ (GtkClassInitFunc) tictactoe_class_init,
+ (GtkObjectInitFunc) tictactoe_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL
+ };
-<tscreen><verb>
-void g_mem_profile( void );
+ ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
+ }
+
+ return ttt_type;
+}
</verb></tscreen>
-Dumps a profile of used memory, but requries that you add #define
-MEM_PROFILE to the top of glib/gmem.c and re-make and make install.
+The GtkTypeInfo structure has the following definition:
<tscreen><verb>
-void g_mem_check( gpointer mem );
+struct _GtkTypeInfo
+{
+ gchar *type_name;
+ guint object_size;
+ guint class_size;
+ GtkClassInitFunc class_init_func;
+ GtkObjectInitFunc object_init_func;
+ GtkArgSetFunc arg_set_func;
+ GtkArgGetFunc arg_get_func;
+};
</verb></tscreen>
-Checks that a memory location is valid. Requires you add #define
-MEM_CHECK to the top of gmem.c and re-make and make install.
+The fields of this structure are pretty self-explanatory. We'll ignore
+the <tt/arg_set_func/ and <tt/arg_get_func/ fields here: they have an important,
+but as yet largely
+unimplemented, role in allowing widget options to be conveniently set
+from interpreted languages. Once GTK has a correctly filled in copy of
+this structure, it knows how to create objects of a particular widget
+type.
<!-- ----------------------------------------------------------------- -->
-<sect1>Timers
+<sect2> The <tt/_class_init()/ function
<p>
-Timer functions..
+The <tt/WIDGETNAME_class_init()/ function initializes the fields of
+the widget's class structure, and sets up any signals for the
+class. For our Tictactoe widget it looks like:
<tscreen><verb>
-GTimer *g_timer_new( void );
-void g_timer_destroy( GTimer *timer );
+enum {
+ TICTACTOE_SIGNAL,
+ LAST_SIGNAL
+};
-void g_timer_start( GTimer *timer );
+static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
-void g_timer_stop( GTimer *timer );
+static void
+tictactoe_class_init (TictactoeClass *class)
+{
+ GtkObjectClass *object_class;
-void g_timer_reset( GTimer *timer );
+ object_class = (GtkObjectClass*) class;
+
+ tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
+ gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
-gdouble g_timer_elapsed( GTimer *timer,
- gulong *microseconds );
-</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect1>String Handling
-<p>
-A whole mess of string handling functions. They all look very interesting, and
-probably better for many purposes than the standard C string functions, but
-require documentation.
+ gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
-<tscreen><verb>
-GString *g_string_new( gchar *init );
+ class->tictactoe = NULL;
+}
+</verb></tscreen>
-void g_string_free( GString *string,
- gint free_segment );
-
-GString *g_string_assign( GString *lval,
- gchar *rval );
-
-GString *g_string_truncate( GString *string,
- gint len );
-
-GString *g_string_append( GString *string,
- gchar *val );
-
-GString *g_string_append_c( GString *string,
- gchar c );
-
-GString *g_string_prepend( GString *string,
- gchar *val );
-
-GString *g_string_prepend_c( GString *string,
- gchar c );
-
-void g_string_sprintf( GString *string,
- gchar *fmt,
- ...);
-
-void g_string_sprintfa ( GString *string,
- gchar *fmt,
- ... );
-</verb></tscreen>
+Our widget has just one signal, the <tt/tictactoe/ signal that is
+invoked when a row, column, or diagonal is completely filled in. Not
+every composite widget needs signals, so if you are reading this for
+the first time, you may want to skip to the next section now, as
+things are going to get a bit complicated.
+
+The function:
-<!-- ----------------------------------------------------------------- -->
-<sect1>Utility and Error Functions
-<p>
<tscreen><verb>
-gchar *g_strdup( const gchar *str );
+gint gtk_signal_new( const gchar *name,
+ GtkSignalRunType run_type,
+ GtkType object_type,
+ gint function_offset,
+ GtkSignalMarshaller marshaller,
+ GtkType return_val,
+ guint nparams,
+ ...);
</verb></tscreen>
-Replacement strdup function. Copies the original strings contents to
-newly allocated memory, and returns a pointer to it.
+Creates a new signal. The parameters are:
-<tscreen><verb>
-gchar *g_strerror( gint errnum );
-</verb></tscreen>
+<itemize>
+<item> <tt/name/: The name of the signal.
+<item> <tt/run_type/: Whether the default handler runs before or after
+user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/,
+although there are other possibilities.
+<item> <tt/object_type/: The ID of the object that this signal applies
+to. (It will also apply to that objects descendents)
+<item> <tt/function_offset/: The offset within the class structure of
+a pointer to the default handler.
+<item> <tt/marshaller/: A function that is used to invoke the signal
+handler. For signal handlers that have no arguments other than the
+object that emitted the signal and user data, we can use the
+pre-supplied marshaller function <tt/gtk_signal_default_marshaller/.
+<item> <tt/return_val/: The type of the return val.
+<item> <tt/nparams/: The number of parameters of the signal handler
+(other than the two default ones mentioned above)
+<item> <tt/.../: The types of the parameters.
+</itemize>
-I recommend using this for all error messages. It's much nicer, and more
-portable than perror() or others. The output is usually of the form:
+When specifying types, the <tt/GtkType/ enumeration is used:
<tscreen><verb>
-program name:function that failed:file or further description:strerror
-</verb></tscreen>
+typedef enum
+{
+ GTK_TYPE_INVALID,
+ GTK_TYPE_NONE,
+ GTK_TYPE_CHAR,
+ GTK_TYPE_BOOL,
+ GTK_TYPE_INT,
+ GTK_TYPE_UINT,
+ GTK_TYPE_LONG,
+ GTK_TYPE_ULONG,
+ GTK_TYPE_FLOAT,
+ GTK_TYPE_DOUBLE,
+ GTK_TYPE_STRING,
+ GTK_TYPE_ENUM,
+ GTK_TYPE_FLAGS,
+ GTK_TYPE_BOXED,
+ GTK_TYPE_FOREIGN,
+ GTK_TYPE_CALLBACK,
+ GTK_TYPE_ARGS,
-Here's an example of one such call used in our hello_world program:
+ GTK_TYPE_POINTER,
-<tscreen><verb>
-g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
-</verb></tscreen>
+ /* it'd be great if the next two could be removed eventually */
+ GTK_TYPE_SIGNAL,
+ GTK_TYPE_C_CALLBACK,
-<tscreen><verb>
-void g_error( gchar *format, ... );
+ GTK_TYPE_OBJECT
+
+} GtkFundamentalType;
</verb></tscreen>
-Prints an error message. The format is just like printf, but it
-prepends "** ERROR **: " to your message, and exits the program.
-Use only for fatal errors.
+<tt/gtk_signal_new()/ returns a unique integer identifier for the
+signal, that we store in the <tt/tictactoe_signals/ array, which we
+index using an enumeration. (Conventionally, the enumeration elements
+are the signal name, uppercased, but here there would be a conflict
+with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/
+instead.
+
+After creating our signals, we need to tell GTK to associate our
+signals with the Tictactoe class. We do that by calling
+<tt/gtk_object_class_add_signals()/. We then set the pointer which
+points to the default handler for the ``tictactoe'' signal to NULL,
+indicating that there is no default action.
+
+<!-- ----------------------------------------------------------------- -->
+<sect2> The <tt/_init()/ function.
+<p>
+Each widget class also needs a function to initialize the object
+structure. Usually, this function has the fairly limited role of
+setting the fields of the structure to default values. For composite
+widgets, however, this function also creates the component widgets.
<tscreen><verb>
-void g_warning( gchar *format, ... );
+static void
+tictactoe_init (Tictactoe *ttt)
+{
+ GtkWidget *table;
+ gint i,j;
+
+ table = gtk_table_new (3, 3, TRUE);
+ gtk_container_add (GTK_CONTAINER(ttt), table);
+ gtk_widget_show (table);
+
+ for (i=0;i<3; i++)
+ for (j=0;j<3; j++)
+ {
+ ttt->buttons[i][j] = gtk_toggle_button_new ();
+ gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
+ i, i+1, j, j+1);
+ gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
+ GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
+ gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
+ gtk_widget_show (ttt->buttons[i][j]);
+ }
+}
</verb></tscreen>
-Same as above, but prepends "** WARNING **: ", and does not exit the
-program.
-
-<tscreen><verb>
-void g_message( gchar *format, ... );
-</verb></tscreen>
+<!-- ----------------------------------------------------------------- -->
+<sect2> And the rest...
+<p>
+There is one more function that every widget (except for base widget
+types like GtkBin that cannot be instantiated) needs to have - the
+function that the user calls to create an object of that type. This is
+conventionally called <tt/WIDGETNAME_new()/. In some
+widgets, though not for the Tictactoe widgets, this function takes
+arguments, and does some setup based on the arguments. The other two
+functions are specific to the Tictactoe widget.
-Prints "message: " prepended to the string you pass in.
+<tt/tictactoe_clear()/ is a public function that resets all the
+buttons in the widget to the up position. Note the use of
+<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for
+button toggles from being triggered unnecessarily.
-<tscreen><verb>
-void g_print( gchar *format, ... );
-</verb></tscreen>
+<tt/tictactoe_toggle()/ is the signal handler that is invoked when the
+user clicks on a button. It checks to see if there are any winning
+combinations that involve the toggled button, and if so, emits
+the "tictactoe" signal.
-Replacement for printf().
+<tscreen><verb>
+GtkWidget*
+tictactoe_new ()
+{
+ return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
+}
-And our last function:
+void
+tictactoe_clear (Tictactoe *ttt)
+{
+ int i,j;
-<tscreen><verb>
-gchar *g_strsignal( gint signum );
-</verb></tscreen>
+ for (i=0;i<3;i++)
+ for (j=0;j<3;j++)
+ {
+ gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
+ gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
+ FALSE);
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
+ }
+}
-Prints out the name of the Unix system signal given the signal number.
-Useful in generic signal handling functions.
+static void
+tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
+{
+ int i,k;
-All of the above are more or less just stolen from glib.h. If anyone cares
-to document any function, just send me an email!
+ static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
+ { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
+ { 0, 1, 2 }, { 0, 1, 2 } };
+ static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
+ { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
+ { 0, 1, 2 }, { 2, 1, 0 } };
-<!-- ***************************************************************** -->
-<sect>GTK's rc Files
-<!-- ***************************************************************** -->
-<p>
-GTK has it's own way of dealing with application defaults, by using rc
-files. These can be used to set the colors of just about any widget, and
-can also be used to tile pixmaps onto the background of some widgets.
+ int success, found;
-<!-- ----------------------------------------------------------------- -->
-<sect1>Functions For rc Files
-<p>
-When your application starts, you should include a call to:
+ for (k=0; k<8; k++)
+ {
+ success = TRUE;
+ found = FALSE;
-<tscreen><verb>
-void gtk_rc_parse( char *filename );
+ for (i=0;i<3;i++)
+ {
+ success = success &&
+ GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
+ found = found ||
+ ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
+ }
+
+ if (success && found)
+ {
+ gtk_signal_emit (GTK_OBJECT (ttt),
+ tictactoe_signals[TICTACTOE_SIGNAL]);
+ break;
+ }
+ }
+}
</verb></tscreen>
-Passing in the filename of your rc file. This will cause GTK to parse this
-file, and use the style settings for the widget types defined there.
-
-If you wish to have a special set of widgets that can take on a different
-style from others, or any other logical division of widgets, use a call to:
+And finally, an example program using our Tictactoe widget:
<tscreen><verb>
-void gtk_widget_set_name( GtkWidget *widget,
- gchar *name );
-</verb></tscreen>
-
-Passing your newly created widget as the first argument, and the name
-you wish to give it as the second. This will allow you to change the
-attributes of this widget by name through the rc file.
+#include <gtk/gtk.h>
+#include "tictactoe.h"
-If we use a call something like this:
+/* Invoked when a row, column or diagonal is completed */
+void
+win (GtkWidget *widget, gpointer data)
+{
+ g_print ("Yay!\n");
+ tictactoe_clear (TICTACTOE (widget));
+}
-<tscreen><verb>
-button = gtk_button_new_with_label ("Special Button");
-gtk_widget_set_name (button, "special button");
-</verb></tscreen>
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *ttt;
+
+ gtk_init (&argc, &argv);
-Then this button is given the name "special button" and may be addressed by
-name in the rc file as "special button.GtkButton". [<--- Verify ME!]
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (gtk_exit), NULL);
+
+ gtk_container_border_width (GTK_CONTAINER (window), 10);
-The example rc file below, sets the properties of the main window, and lets
-all children of that main window inherit the style described by the "main
-button" style. The code used in the application is:
+ /* Create a new Tictactoe widget */
+ ttt = tictactoe_new ();
+ gtk_container_add (GTK_CONTAINER (window), ttt);
+ gtk_widget_show (ttt);
-<tscreen><verb>
-window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-gtk_widget_set_name (window, "main window");
-</verb></tscreen>
+ /* And attach to its "tictactoe" signal */
+ gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
+ GTK_SIGNAL_FUNC (win), NULL);
-And then the style is defined in the rc file using:
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
-<tscreen><verb>
-widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
-Which sets all the GtkButton widgets in the "main window" to the
-"main_buttons" style as defined in the rc file.
-
-As you can see, this is a fairly powerful and flexible system. Use your
-imagination as to how best to take advantage of this.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Creating a widget from scratch.
<!-- ----------------------------------------------------------------- -->
-<sect1>GTK's rc File Format
+<sect2> Introduction
<p>
-The format of the GTK file is illustrated in the example below. This is
-the testgtkrc file from the GTK distribution, but I've added a
-few comments and things. You may wish to include this explanation
-your application to allow the user to fine tune his application.
-
-There are several directives to change the attributes of a widget.
-
-<itemize>
-<item>fg - Sets the foreground color of a widget.
-<item>bg - Sets the background color of a widget.
-<item>bg_pixmap - Sets the background of a widget to a tiled pixmap.
-<item>font - Sets the font to be used with the given widget.
-</itemize>
+In this section, we'll learn more about how widgets display themselves
+on the screen and interact with events. As an example of this, we'll
+create an analog dial widget with a pointer that the user can drag to
+set the value.
-In addition to this, there are several states a widget can be in, and you
-can set different colors, pixmaps and fonts for each state. These states are:
+<!-- ----------------------------------------------------------------- -->
+<sect2> Displaying a widget on the screen
+<p>
+There are several steps that are involved in displaying on the screen.
+After the widget is created with a call to <tt/WIDGETNAME_new()/,
+several more functions are needed:
<itemize>
-<item>NORMAL - The normal state of a widget, without the mouse over top of
-it, and not being pressed etc.
-<item>PRELIGHT - When the mouse is over top of the widget, colors defined
-using this state will be in effect.
-<item>ACTIVE - When the widget is pressed or clicked it will be active, and
-the attributes assigned by this tag will be in effect.
-<item>INSENSITIVE - When a widget is set insensitive, and cannot be
-activated, it will take these attributes.
-<item>SELECTED - When an object is selected, it takes these attributes.
+<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X
+window for the widget if it has one.
+<item> <tt/WIDGETNAME_map()/ is invoked after the user calls
+<tt/gtk_widget_show()/. It is responsible for making sure the widget
+is actually drawn on the screen (<em/mapped/). For a container class,
+it must also make calls to <tt/map()/> functions of any child widgets.
+<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/
+is called for the widget or one of its ancestors. It makes the actual
+calls to the drawing functions to draw the widget on the screen. For
+container widgets, this function must make calls to
+<tt/gtk_widget_draw()/ for its child widgets.
+<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the
+widget. It makes the necessary calls to the drawing functions to draw
+the exposed portion on the screen. For container widgets, this
+function must generate expose events for its child widgets which don't
+have their own windows. (If they have their own windows, then X will
+generate the necessary expose events)
</itemize>
-When using the "fg" and "bg" keywords to set the colors of widgets, the
-format is:
+You might notice that the last two functions are quite similar - each
+is responsible for drawing the widget on the screen. In fact many
+types of widgets don't really care about the difference between the
+two. The default <tt/draw()/ function in the widget class simply
+generates a synthetic expose event for the redrawn area. However, some
+types of widgets can save work by distinguishing between the two
+functions. For instance, if a widget has multiple X windows, then
+since expose events identify the exposed window, it can redraw only
+the affected window, which is not possible for calls to <tt/draw()/.
-<tscreen><verb>
-fg[<STATE>] = { Red, Green, Blue }
-</verb></tscreen>
+Container widgets, even if they don't care about the difference for
+themselves, can't simply use the default <tt/draw()/ function because
+their child widgets might care about the difference. However,
+it would be wasteful to duplicate the drawing code between the two
+functions. The convention is that such widgets have a function called
+<tt/WIDGETNAME_paint()/ that does the actual work of drawing the
+widget, that is then called by the <tt/draw()/ and <tt/expose()/
+functions.
-Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red,
-Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being
-white. They must be in float form, or they will register as 0, so a straight
-"1" will not work, it must be "1.0". A straight "0" is fine because it
-doesn't matter if it's not recognized. Unrecognized values are set to 0.
+In our example approach, since the dial widget is not a container
+widget, and only has a single window, we can take the simplest
+approach and use the default <tt/draw()/ function and only implement
+an <tt/expose()/ function.
-bg_pixmap is very similar to the above, except the colors are replaced by a
-filename.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The origins of the Dial Widget
+<p>
+Just as all land animals are just variants on the first amphibian that
+crawled up out of the mud, Gtk widgets tend to start off as variants
+of some other, previously written widget. Thus, although this section
+is entilted ``Creating a Widget from Scratch'', the Dial widget really
+began with the source code for the Range widget. This was picked as a
+starting point because it would be nice if our Dial had the same
+interface as the Scale widgets which are just specialized descendents
+of the Range widget. So, though the source code is presented below in
+finished form, it should not be implied that it was written, <em>deus
+ex machina</em> in this fashion. Also, if you aren't yet familiar with
+how scale widgets work from the application writer's point of view, it
+would be a good idea to look them over before continuing.
-pixmap_path is a list of paths seperated by ":"'s. These paths will be
-searched for any pixmap you specify.
+<!-- ----------------------------------------------------------------- -->
+<sect2> The Basics
+<p>
+Quite a bit of our widget should look pretty familiar from the
+Tictactoe widget. First, we have a header file:
-The font directive is simply:
<tscreen><verb>
-font = "<font name>"
-</verb></tscreen>
-
-Where the only hard part is figuring out the font string. Using xfontsel or
-similar utility should help.
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
-The "widget_class" sets the style of a class of widgets. These classes are
-listed in the widget overview on the class hierarchy.
+#ifndef __GTK_DIAL_H__
+#define __GTK_DIAL_H__
-The "widget" directive sets a specificaly named set of widgets to a
-given style, overriding any style set for the given widget class.
-These widgets are registered inside the application using the
-gtk_widget_set_name() call. This allows you to specify the attributes of a
-widget on a per widget basis, rather than setting the attributes of an
-entire widget class. I urge you to document any of these special widgets so
-users may customize them.
+#include <gdk/gdk.h>
+#include <gtk/gtkadjustment.h>
+#include <gtk/gtkwidget.h>
-When the keyword <tt>parent</> is used as an attribute, the widget will take on
-the attributes of it's parent in the application.
-When defining a style, you may assign the attributes of a previously defined
-style to this new one.
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
-<tscreen><verb>
-style "main_button" = "button"
-{
- font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
- bg[PRELIGHT] = { 0.75, 0, 0 }
-}
-</verb></tscreen>
-This example takes the "button" style, and creates a new "main_button" style
-simply by changing the font and prelight background color of the "button"
-style.
+#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
+#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
+#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
-Of course, many of these attributes don't apply to all widgets. It's a
-simple matter of common sense really. Anything that could apply, should.
-<!-- ----------------------------------------------------------------- -->
-<sect1>Example rc file
-<p>
+typedef struct _GtkDial GtkDial;
+typedef struct _GtkDialClass GtkDialClass;
-<tscreen><verb>
-# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
-#
-pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
-#
-# style <name> [= <name>]
-# {
-# <option>
-# }
-#
-# widget <widget_set> style <style_name>
-# widget_class <widget_class_set> style <style_name>
+struct _GtkDial
+{
+ GtkWidget widget;
+ /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
+ guint policy : 2;
-# Here is a list of all the possible states. Note that some do not apply to
-# certain widgets.
-#
-# NORMAL - The normal state of a widget, without the mouse over top of
-# it, and not being pressed etc.
-#
-# PRELIGHT - When the mouse is over top of the widget, colors defined
-# using this state will be in effect.
-#
-# ACTIVE - When the widget is pressed or clicked it will be active, and
-# the attributes assigned by this tag will be in effect.
-#
-# INSENSITIVE - When a widget is set insensitive, and cannot be
-# activated, it will take these attributes.
-#
-# SELECTED - When an object is selected, it takes these attributes.
-#
-# Given these states, we can set the attributes of the widgets in each of
-# these states using the following directives.
-#
-# fg - Sets the foreground color of a widget.
-# fg - Sets the background color of a widget.
-# bg_pixmap - Sets the background of a widget to a tiled pixmap.
-# font - Sets the font to be used with the given widget.
-#
+ /* Button currently pressed or 0 if none */
+ guint8 button;
-# This sets a style called "button". The name is not really important, as
-# it is assigned to the actual widgets at the bottom of the file.
+ /* Dimensions of dial components */
+ gint radius;
+ gint pointer_width;
-style "window"
-{
- #This sets the padding around the window to the pixmap specified.
- #bg_pixmap[<STATE>] = "<pixmap filename>"
- bg_pixmap[NORMAL] = "warning.xpm"
-}
+ /* ID of update timer, or 0 if none */
+ guint32 timer;
-style "scale"
-{
- #Sets the foreground color (font color) to red when in the "NORMAL"
- #state.
-
- fg[NORMAL] = { 1.0, 0, 0 }
-
- #Sets the background pixmap of this widget to that of it's parent.
- bg_pixmap[NORMAL] = "<parent>"
-}
+ /* Current angle */
+ gfloat angle;
-style "button"
-{
- # This shows all the possible states for a button. The only one that
- # doesn't apply is the SELECTED state.
-
- fg[PRELIGHT] = { 0, 1.0, 1.0 }
- bg[PRELIGHT] = { 0, 0, 1.0 }
- bg[ACTIVE] = { 1.0, 0, 0 }
- fg[ACTIVE] = { 0, 1.0, 0 }
- bg[NORMAL] = { 1.0, 1.0, 0 }
- fg[NORMAL] = { .99, 0, .99 }
- bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
- fg[INSENSITIVE] = { 1.0, 0, 1.0 }
-}
+ /* Old values from adjustment stored so we know when something changes */
+ gfloat old_value;
+ gfloat old_lower;
+ gfloat old_upper;
-# In this example, we inherit the attributes of the "button" style and then
-# override the font and background color when prelit to create a new
-# "main_button" style.
+ /* The adjustment object that stores the data for this dial */
+ GtkAdjustment *adjustment;
+};
-style "main_button" = "button"
+struct _GtkDialClass
{
- font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
- bg[PRELIGHT] = { 0.75, 0, 0 }
-}
+ GtkWidgetClass parent_class;
+};
-style "toggle_button" = "button"
-{
- fg[NORMAL] = { 1.0, 0, 0 }
- fg[ACTIVE] = { 1.0, 0, 0 }
-
- # This sets the background pixmap of the toggle_button to that of it's
- # parent widget (as defined in the application).
- bg_pixmap[NORMAL] = "<parent>"
-}
-style "text"
-{
- bg_pixmap[NORMAL] = "marble.xpm"
- fg[NORMAL] = { 1.0, 1.0, 1.0 }
-}
+GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
+guint gtk_dial_get_type (void);
+GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
+void gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy);
-style "ruler"
-{
- font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
+void gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment);
+#ifdef __cplusplus
}
+#endif /* __cplusplus */
-# pixmap_path "~/.pixmaps"
-# These set the widget types to use the styles defined above.
-# The widget types are listed in the class hierarchy, but could probably be
-# just listed in this document for the users reference.
+#endif /* __GTK_DIAL_H__ */
+</verb></tscreen>
-widget_class "GtkWindow" style "window"
-widget_class "GtkDialog" style "window"
-widget_class "GtkFileSelection" style "window"
-widget_class "*Gtk*Scale" style "scale"
-widget_class "*GtkCheckButton*" style "toggle_button"
-widget_class "*GtkRadioButton*" style "toggle_button"
-widget_class "*GtkButton*" style "button"
-widget_class "*Ruler" style "ruler"
-widget_class "*GtkText" style "text"
+Since there is quite a bit more going on in this widget, than the last
+one, we have more fields in the data structure, but otherwise things
+are pretty similar.
-# This sets all the buttons that are children of the "main window" to
-# the main_buton style. These must be documented to be taken advantage of.
-widget "main window.*GtkButton*" style "main_button"
-</verb></tscreen>
+Next, after including header files, and declaring a few constants,
+we have some functions to provide information about the widget
+and initialize it:
-<!-- ***************************************************************** -->
-<sect>Writing Your Own Widgets
-<!-- ***************************************************************** -->
+<tscreen><verb>
+#include <math.h>
+#include <stdio.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
-<p>
-Although the GTK distribution comes with many types of widgets that
-should cover most basic needs, there may come a time when you need to
-create your own new widget type. Since GTK uses widget inheretence
-extensively, and there is already a widget that is close to what you want,
-it is often possible to make a useful new widget type in
-just a few lines of code. But before starting work on a new widget, check
-around first to make sure that someone has not already written
-it. This will prevent duplication of effort and keep the number of
-GTK widgets out there to a minimum, which will help keep both the code
-and the interface of different applications consistent. As a flip side
-to this, once you finish your widget, announce it to the world so
-other people can benefit. The best place to do this is probably the
-<tt>gtk-list</tt>.
+#include "gtkdial.h"
-Complete sources for the example widgets are available at the place you
-got this tutorial, or from:
+#define SCROLL_DELAY_LENGTH 300
+#define DIAL_DEFAULT_SIZE 100
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+/* Forward declararations */
+[ omitted to save space ]
-<!-- ----------------------------------------------------------------- -->
-<sect1> The Anatomy Of A Widget
-<p>
-In order to create a new widget, it is important to have an
-understanding of how GTK objects work. This section is just meant as a
-brief overview. See the reference documentation for the details.
+/* Local data */
-GTK widgets are implemented in an object oriented fashion. However,
-they are implemented in standard C. This greatly improves portability
-and stability over using current generation C++ compilers; however,
-it does mean that the widget writer has to pay attention to some of
-the implementation details. The information common to all instances of
-one class of widgets (e.g., to all Button widgets) is stored in the
-<em>class structure</em>. There is only one copy of this in
-which is stored information about the class's signals
-(which act like virtual functions in C). To support inheritance, the
-first field in the class structure must be a copy of the parent's
-class structure. The declaration of the class structure of GtkButtton
-looks like:
+static GtkWidgetClass *parent_class = NULL;
-<tscreen><verb>
-struct _GtkButtonClass
+guint
+gtk_dial_get_type ()
{
- GtkContainerClass parent_class;
+ static guint dial_type = 0;
- void (* pressed) (GtkButton *button);
- void (* released) (GtkButton *button);
- void (* clicked) (GtkButton *button);
- void (* enter) (GtkButton *button);
- void (* leave) (GtkButton *button);
-};
-</verb></tscreen>
+ if (!dial_type)
+ {
+ GtkTypeInfo dial_info =
+ {
+ "GtkDial",
+ sizeof (GtkDial),
+ sizeof (GtkDialClass),
+ (GtkClassInitFunc) gtk_dial_class_init,
+ (GtkObjectInitFunc) gtk_dial_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL,
+ };
-When a button is treated as a container (for instance, when it is
-resized), its class structure can be cast to GtkContainerClass, and
-the relevant fields used to handle the signals.
+ dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
+ }
-There is also a structure for each widget that is created on a
-per-instance basis. This structure has fields to store information that
-is different for each instance of the widget. We'll call this
-structure the <em>object structure</em>. For the Button class, it looks
-like:
+ return dial_type;
+}
-<tscreen><verb>
-struct _GtkButton
+static void
+gtk_dial_class_init (GtkDialClass *class)
{
- GtkContainer container;
-
- GtkWidget *child;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
- guint in_button : 1;
- guint button_down : 1;
-};
-</verb></tscreen>
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
-Note that, similar to the class structure, the first field is the
-object structure of the parent class, so that this structure can be
-cast to the parent class's object structure as needed.
+ parent_class = gtk_type_class (gtk_widget_get_type ());
-<!-- ----------------------------------------------------------------- -->
-<sect1> Creating a Composite widget
+ object_class->destroy = gtk_dial_destroy;
-<!-- ----------------------------------------------------------------- -->
-<sect2> Introduction
-<p>
-One type of widget that you may be interested in creating is a
-widget that is merely an aggregate of other GTK widgets. This type of
-widget does nothing that couldn't be done without creating new
-widgets, but provides a convenient way of packaging user interface
-elements for reuse. The FileSelection and ColorSelection widgets in
-the standard distribution are examples of this type of widget.
+ widget_class->realize = gtk_dial_realize;
+ widget_class->expose_event = gtk_dial_expose;
+ widget_class->size_request = gtk_dial_size_request;
+ widget_class->size_allocate = gtk_dial_size_allocate;
+ widget_class->button_press_event = gtk_dial_button_press;
+ widget_class->button_release_event = gtk_dial_button_release;
+ widget_class->motion_notify_event = gtk_dial_motion_notify;
+}
-The example widget that we'll create in this section is the Tictactoe
-widget, a 3x3 array of toggle buttons which triggers a signal when all
-three buttons in a row, column, or on one of the diagonals are
-depressed.
+static void
+gtk_dial_init (GtkDial *dial)
+{
+ dial->button = 0;
+ dial->policy = GTK_UPDATE_CONTINUOUS;
+ dial->timer = 0;
+ dial->radius = 0;
+ dial->pointer_width = 0;
+ dial->angle = 0.0;
+ dial->old_value = 0.0;
+ dial->old_lower = 0.0;
+ dial->old_upper = 0.0;
+ dial->adjustment = NULL;
+}
-<!-- ----------------------------------------------------------------- -->
-<sect2> Choosing a parent class
-<p>
-The parent class for a composite widget is typically the container
-class that holds all of the elements of the composite widget. For
-example, the parent class of the FileSelection widget is the
-Dialog class. Since our buttons will be arranged in a table, it
-might seem natural to make our parent class the GtkTable
-class. Unfortunately, this turns out not to work. The creation of a
-widget is divided among two functions - a <tt/WIDGETNAME_new()/
-function that the user calls, and a <tt/WIDGETNAME_init()/ function
-which does the basic work of initializing the widget which is
-independent of the arguments passed to the <tt/_new()/
-function. Descendent widgets only call the <tt/_init/ function of
-their parent widget. But this division of labor doesn't work well for
-tables, which when created, need to know the number of rows and
-columns in the table. Unless we want to duplicate most of the
-functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had
-best avoid deriving it from GtkTable. For that reason, we derive it
-from GtkVBox instead, and stick our table inside the VBox.
+GtkWidget*
+gtk_dial_new (GtkAdjustment *adjustment)
+{
+ GtkDial *dial;
-<!-- ----------------------------------------------------------------- -->
-<sect2> The header file
-<p>
-Each widget class has a header file which declares the object and
-class structures for that widget, along with public functions.
-A couple of features are worth pointing out. To prevent duplicate
-definitions, we wrap the entire header file in:
+ dial = gtk_type_new (gtk_dial_get_type ());
-<tscreen><verb>
-#ifndef __TICTACTOE_H__
-#define __TICTACTOE_H__
-.
-.
-.
-#endif /* __TICTACTOE_H__ */
-</verb></tscreen>
+ if (!adjustment)
+ adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-And to keep C++ programs that include the header file happy, in:
+ gtk_dial_set_adjustment (dial, adjustment);
-<tscreen><verb>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-.
-.
-.
-#ifdef __cplusplus
+ return GTK_WIDGET (dial);
}
-#endif /* __cplusplus */
-</verb></tscreen>
-Along with the functions and structures, we declare three standard
-macros in our header file, <tt/TICTACTOE(obj)/,
-<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a
-pointer into a pointer to the object or class structure, and check
-if an object is a Tictactoe widget respectively.
+static void
+gtk_dial_destroy (GtkObject *object)
+{
+ GtkDial *dial;
-Here is the complete header file:
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_DIAL (object));
-<tscreen><verb>
-/* tictactoe.h */
+ dial = GTK_DIAL (object);
-#ifndef __TICTACTOE_H__
-#define __TICTACTOE_H__
+ if (dial->adjustment)
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
-#include <gdk/gdk.h>
-#include <gtk/gtkvbox.h>
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+</verb></tscreen>
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+Note that this <tt/init()/ function does less than for the Tictactoe
+widget, since this is not a composite widget, and the <tt/new()/
+function does more, since it now has an argument. Also, note that when
+we store a pointer to the Adjustment object, we increment its
+reference count, (and correspondingly decrement when we no longer use
+it) so that GTK can keep track of when it can be safely destroyed.
-#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
-#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
-#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
+<p>
+Also, there are a few function to manipulate the widget's options:
+<tscreen><verb>
+GtkAdjustment*
+gtk_dial_get_adjustment (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-typedef struct _Tictactoe Tictactoe;
-typedef struct _TictactoeClass TictactoeClass;
+ return dial->adjustment;
+}
-struct _Tictactoe
+void
+gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy)
{
- GtkVBox vbox;
-
- GtkWidget *buttons[3][3];
-};
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
+
+ dial->policy = policy;
+}
-struct _TictactoeClass
+void
+gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment)
{
- GtkVBoxClass parent_class;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
- void (* tictactoe) (Tictactoe *ttt);
-};
+ if (dial->adjustment)
+ {
+ gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
+ }
-guint tictactoe_get_type (void);
-GtkWidget* tictactoe_new (void);
-void tictactoe_clear (Tictactoe *ttt);
+ dial->adjustment = adjustment;
+ gtk_object_ref (GTK_OBJECT (dial->adjustment));
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+ gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
+ (GtkSignalFunc) gtk_dial_adjustment_changed,
+ (gpointer) dial);
+ gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
+ (GtkSignalFunc) gtk_dial_adjustment_value_changed,
+ (gpointer) dial);
-#endif /* __TICTACTOE_H__ */
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ gtk_dial_update (dial);
+}
</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_get_type()/ function.
+<sect2> <tt/gtk_dial_realize()/
+
<p>
-We now continue on to the implementation of our widget. A core
-function for every widget is the function
-<tt/WIDGETNAME_get_type()/. This function, when first called, tells
-GTK about the widget class, and gets an ID that uniquely identifies
-the widget class. Upon subsequent calls, it just returns the ID.
+Now we come to some new types of functions. First, we have a function
+that does the work of creating the X window. Notice that a mask is
+passed to the function <tt/gdk_window_new()/ which specifies which fields of
+the GdkWindowAttr structure actually have data in them (the remaining
+fields wll be given default values). Also worth noting is the way the
+event mask of the widget is created. We call
+<tt/gtk_widget_get_events()/ to retrieve the event mask that the user
+has specified for this widget (with <tt/gtk_widget_set_events()/, and
+add the events that we are interested in ourselves.
+
+<p>
+After creating the window, we set its style and background, and put a
+pointer to the widget in the user data field of the GdkWindow. This
+last step allows GTK to dispatch events for this window to the correct
+widget.
<tscreen><verb>
-guint
-tictactoe_get_type ()
+static void
+gtk_dial_realize (GtkWidget *widget)
{
- static guint ttt_type = 0;
+ GtkDial *dial;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
- if (!ttt_type)
- {
- GtkTypeInfo ttt_info =
- {
- "Tictactoe",
- sizeof (Tictactoe),
- sizeof (TictactoeClass),
- (GtkClassInitFunc) tictactoe_class_init,
- (GtkObjectInitFunc) tictactoe_init,
- (GtkArgSetFunc) NULL,
- (GtkArgGetFunc) NULL
- };
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
- ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
- }
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+ dial = GTK_DIAL (widget);
- return ttt_type;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+ widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ gdk_window_set_user_data (widget->window, widget);
+
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
</verb></tscreen>
-The GtkTypeInfo structure has the following definition:
+<sect2> Size negotiation
+
+<p>
+Before the first time that the window containing a widget is
+displayed, and whenever the layout of the window changes, GTK asks
+each child widget for its desired size. This request is handled by the
+function, <tt/gtk_dial_size_request()/. Since our widget isn't a
+container widget, and has no real constraints on its size, we just
+return a reasonable default value.
<tscreen><verb>
-struct _GtkTypeInfo
+static void
+gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
{
- gchar *type_name;
- guint object_size;
- guint class_size;
- GtkClassInitFunc class_init_func;
- GtkObjectInitFunc object_init_func;
- GtkArgSetFunc arg_set_func;
- GtkArgGetFunc arg_get_func;
-};
+ requisition->width = DIAL_DEFAULT_SIZE;
+ requisition->height = DIAL_DEFAULT_SIZE;
+}
</verb></tscreen>
-The fields of this structure are pretty self-explanatory. We'll ignore
-the <tt/arg_set_func/ and <tt/arg_get_func/ fields here: they have an important,
-but as yet largely
-unimplemented, role in allowing widget options to be conveniently set
-from interpreted languages. Once GTK has a correctly filled in copy of
-this structure, it knows how to create objects of a particular widget
-type.
-
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_class_init()/ function
<p>
-The <tt/WIDGETNAME_class_init()/ function initializes the fields of
-the widget's class structure, and sets up any signals for the
-class. For our Tictactoe widget it looks like:
+After all the widgets have requested an ideal size, the layout of the
+window is computed and each child widget is notified of its actual
+size. Usually, this will at least as large as the requested size, but
+if for instance, the user has resized the window, it may occasionally
+be smaller than the requested size. The size notification is handled
+by the function <tt/gtk_dial_size_allocate()/. Notice that as well as
+computing the sizes of some component pieces for future use, this
+routine also does the grunt work of moving the widgets X window into
+the new position and size.
<tscreen><verb>
-
-enum {
- TICTACTOE_SIGNAL,
- LAST_SIGNAL
-};
-
-static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
-
static void
-tictactoe_class_init (TictactoeClass *class)
+gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
{
- GtkObjectClass *object_class;
+ GtkDial *dial;
- object_class = (GtkObjectClass*) class;
-
- tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
- GTK_RUN_FIRST,
- object_class->type,
- GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
- gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
+ g_return_if_fail (allocation != NULL);
+ widget->allocation = *allocation;
+ if (GTK_WIDGET_REALIZED (widget))
+ {
+ dial = GTK_DIAL (widget);
- gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
- class->tictactoe = NULL;
+ dial->radius = MAX(allocation->width,allocation->height) * 0.45;
+ dial->pointer_width = dial->radius / 5;
+ }
}
-</verb></tscreen>
+</verb></tscreen>.
-Our widget has just one signal, the <tt/tictactoe/ signal that is
-invoked when a row, column, or diagonal is completely filled in. Not
-every composite widget needs signals, so if you are reading this for
-the first time, you may want to skip to the next section now, as
-things are going to get a bit complicated.
+<!-- ----------------------------------------------------------------- -->
+<sect2> <tt/gtk_dial_expose()/
-The function:
+<p>
+As mentioned above, all the drawing of this widget is done in the
+handler for expose events. There's not much to remark on here except
+the use of the function <tt/gtk_draw_polygon/ to draw the pointer with
+three dimensional shading according to the colors stored in the
+widget's style.
<tscreen><verb>
-gint gtk_signal_new( const gchar *name,
- GtkSignalRunType run_type,
- GtkType object_type,
- gint function_offset,
- GtkSignalMarshaller marshaller,
- GtkType return_val,
- guint nparams,
- ...);
-</verb></tscreen>
+static gint
+gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GtkDial *dial;
+ GdkPoint points[3];
+ gdouble s,c;
+ gdouble theta;
+ gint xc, yc;
+ gint tick_length;
+ gint i;
-Creates a new signal. The parameters are:
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
-<itemize>
-<item> <tt/name/: The name of the signal.
-<item> <tt/run_type/: Whether the default handler runs before or after
-user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/,
-although there are other possibilities.
-<item> <tt/object_type/: The ID of the object that this signal applies
-to. (It will also apply to that objects descendents)
-<item> <tt/function_offset/: The offset within the class structure of
-a pointer to the default handler.
-<item> <tt/marshaller/: A function that is used to invoke the signal
-handler. For signal handlers that have no arguments other than the
-object that emitted the signal and user data, we can use the
-pre-supplied marshaller function <tt/gtk_signal_default_marshaller/.
-<item> <tt/return_val/: The type of the return val.
-<item> <tt/nparams/: The number of parameters of the signal handler
-(other than the two default ones mentioned above)
-<item> <tt/.../: The types of the parameters.
-</itemize>
+ if (event->count > 0)
+ return FALSE;
+
+ dial = GTK_DIAL (widget);
-When specifying types, the <tt/GtkType/ enumeration is used:
+ gdk_window_clear_area (widget->window,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
-<tscreen><verb>
-typedef enum
-{
- GTK_TYPE_INVALID,
- GTK_TYPE_NONE,
- GTK_TYPE_CHAR,
- GTK_TYPE_BOOL,
- GTK_TYPE_INT,
- GTK_TYPE_UINT,
- GTK_TYPE_LONG,
- GTK_TYPE_ULONG,
- GTK_TYPE_FLOAT,
- GTK_TYPE_DOUBLE,
- GTK_TYPE_STRING,
- GTK_TYPE_ENUM,
- GTK_TYPE_FLAGS,
- GTK_TYPE_BOXED,
- GTK_TYPE_FOREIGN,
- GTK_TYPE_CALLBACK,
- GTK_TYPE_ARGS,
+ xc = widget->allocation.width/2;
+ yc = widget->allocation.height/2;
- GTK_TYPE_POINTER,
+ /* Draw ticks */
- /* it'd be great if the next two could be removed eventually */
- GTK_TYPE_SIGNAL,
- GTK_TYPE_C_CALLBACK,
+ for (i=0; i<25; i++)
+ {
+ theta = (i*M_PI/18. - M_PI/6.);
+ s = sin(theta);
+ c = cos(theta);
- GTK_TYPE_OBJECT
+ tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
+
+ gdk_draw_line (widget->window,
+ widget->style->fg_gc[widget->state],
+ xc + c*(dial->radius - tick_length),
+ yc - s*(dial->radius - tick_length),
+ xc + c*dial->radius,
+ yc - s*dial->radius);
+ }
-} GtkFundamentalType;
-</verb></tscreen>
+ /* Draw pointer */
-<tt/gtk_signal_new()/ returns a unique integer identifier for the
-signal, that we store in the <tt/tictactoe_signals/ array, which we
-index using an enumeration. (Conventionally, the enumeration elements
-are the signal name, uppercased, but here there would be a conflict
-with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/
-instead.
+ s = sin(dial->angle);
+ c = cos(dial->angle);
-After creating our signals, we need to tell GTK to associate our
-signals with the Tictactoe class. We do that by calling
-<tt/gtk_object_class_add_signals()/. We then set the pointer which
-points to the default handler for the ``tictactoe'' signal to NULL,
-indicating that there is no default action.
-<!-- ----------------------------------------------------------------- -->
-<sect2> The <tt/_init()/ function.
-<p>
-Each widget class also needs a function to initialize the object
-structure. Usually, this function has the fairly limited role of
-setting the fields of the structure to default values. For composite
-widgets, however, this function also creates the component widgets.
+ points[0].x = xc + s*dial->pointer_width/2;
+ points[0].y = yc + c*dial->pointer_width/2;
+ points[1].x = xc + c*dial->radius;
+ points[1].y = yc - s*dial->radius;
+ points[2].x = xc - s*dial->pointer_width/2;
+ points[2].y = yc - c*dial->pointer_width/2;
-<tscreen><verb>
-static void
-tictactoe_init (Tictactoe *ttt)
-{
- GtkWidget *table;
- gint i,j;
+ gtk_draw_polygon (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ points, 3,
+ TRUE);
- table = gtk_table_new (3, 3, TRUE);
- gtk_container_add (GTK_CONTAINER(ttt), table);
- gtk_widget_show (table);
-
- for (i=0;i<3; i++)
- for (j=0;j<3; j++)
- {
- ttt->buttons[i][j] = gtk_toggle_button_new ();
- gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
- i, i+1, j, j+1);
- gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
- GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
- gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
- gtk_widget_show (ttt->buttons[i][j]);
- }
+ return FALSE;
}
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect2> And the rest...
+<sect2> Event handling
+
<p>
-There is one more function that every widget (except for base widget
-types like GtkBin that cannot be instantiated) needs to have - the
-function that the user calls to create an object of that type. This is
-conventionally called <tt/WIDGETNAME_new()/. In some
-widgets, though not for the Tictactoe widgets, this function takes
-arguments, and does some setup based on the arguments. The other two
-functions are specific to the Tictactoe widget.
-<tt/tictactoe_clear()/ is a public function that resets all the
-buttons in the widget to the up position. Note the use of
-<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for
-button toggles from being triggered unnecessarily.
+The rest of the widget's code handles various types of events, and
+isn't too different from what would be found in many GTK
+applications. Two types of events can occur - either the user can
+click on the widget with the mouse and drag to move the pointer, or
+the value of the Adjustment object can change due to some external
+circumstance.
-<tt/tictactoe_toggle()/ is the signal handler that is invoked when the
-user clicks on a button. It checks to see if there are any winning
-combinations that involve the toggled button, and if so, emits
-the "tictactoe" signal.
+<p>
+When the user clicks on the widget, we check to see if the click was
+appropriately near the pointer, and if so, store then button that the
+user clicked with in the <tt/button/ field of the widget
+structure, and grab all mouse events with a call to
+<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the
+value of the control to be recomputed (by the function
+<tt/gtk_dial_update_mouse/). Depending on the policy that has been
+set, "value_changed" events are either generated instantly
+(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with
+<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the
+button is released (<tt/GTK_UPDATE_DISCONTINUOUS/).
-<tscreen><verb>
-GtkWidget*
-tictactoe_new ()
+<tscreen><verb>
+static gint
+gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event)
{
- return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
+ GtkDial *dial;
+ gint dx, dy;
+ double s, c;
+ double d_parallel;
+ double d_perpendicular;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ /* Determine if button press was within pointer region - we
+ do this by computing the parallel and perpendicular distance of
+ the point where the mouse was pressed from the line passing through
+ the pointer */
+
+ dx = event->x - widget->allocation.width / 2;
+ dy = widget->allocation.height / 2 - event->y;
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+ d_parallel = s*dy + c*dx;
+ d_perpendicular = fabs(s*dx - c*dy);
+
+ if (!dial->button &&
+ (d_perpendicular < dial->pointer_width/2) &&
+ (d_parallel > - dial->pointer_width))
+ {
+ gtk_grab_add (widget);
+
+ dial->button = event->button;
+
+ gtk_dial_update_mouse (dial, event->x, event->y);
+ }
+
+ return FALSE;
}
-void
-tictactoe_clear (Tictactoe *ttt)
+static gint
+gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event)
{
- int i,j;
+ GtkDial *dial;
- for (i=0;i<3;i++)
- for (j=0;j<3;j++)
- {
- gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
- gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
- FALSE);
- gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
- }
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ if (dial->button == event->button)
+ {
+ gtk_grab_remove (widget);
+
+ dial->button = 0;
+
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_timeout_remove (dial->timer);
+
+ if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
+ (dial->old_value != dial->adjustment->value))
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+
+ return FALSE;
}
-static void
-tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
+static gint
+gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
{
- int i,k;
+ GtkDial *dial;
+ GdkModifierType mods;
+ gint x, y, mask;
- static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
- { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
- { 0, 1, 2 }, { 0, 1, 2 } };
- static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
- { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
- { 0, 1, 2 }, { 2, 1, 0 } };
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
- int success, found;
+ dial = GTK_DIAL (widget);
- for (k=0; k<8; k++)
+ if (dial->button != 0)
{
- success = TRUE;
- found = FALSE;
+ x = event->x;
+ y = event->y;
- for (i=0;i<3;i++)
- {
- success = success &&
- GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
- found = found ||
- ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
- }
-
- if (success && found)
+ if (event->is_hint || (event->window != widget->window))
+ gdk_window_get_pointer (widget->window, &x, &y, &mods);
+
+ switch (dial->button)
{
- gtk_signal_emit (GTK_OBJECT (ttt),
- tictactoe_signals[TICTACTOE_SIGNAL]);
+ case 1:
+ mask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ mask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ mask = GDK_BUTTON3_MASK;
+ break;
+ default:
+ mask = 0;
break;
}
- }
-}
-</verb></tscreen>
-And finally, an example program using our Tictactoe widget:
-
-<tscreen><verb>
-#include <gtk/gtk.h>
-#include "tictactoe.h"
+ if (mods & mask)
+ gtk_dial_update_mouse (dial, x,y);
+ }
-/* Invoked when a row, column or diagonal is completed */
-void
-win (GtkWidget *widget, gpointer data)
-{
- g_print ("Yay!\n");
- tictactoe_clear (TICTACTOE (widget));
+ return FALSE;
}
-int
-main (int argc, char *argv[])
+static gint
+gtk_dial_timer (GtkDial *dial)
{
- GtkWidget *window;
- GtkWidget *ttt;
-
- gtk_init (&argc, &argv);
+ g_return_val_if_fail (dial != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
-
- gtk_signal_connect (GTK_OBJECT (window), "destroy",
- GTK_SIGNAL_FUNC (gtk_exit), NULL);
-
- gtk_container_border_width (GTK_CONTAINER (window), 10);
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- /* Create a new Tictactoe widget */
- ttt = tictactoe_new ();
- gtk_container_add (GTK_CONTAINER (window), ttt);
- gtk_widget_show (ttt);
+ return FALSE;
+}
- /* And attach to its "tictactoe" signal */
- gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
- GTK_SIGNAL_FUNC (win), NULL);
+static void
+gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+{
+ gint xc, yc;
+ gfloat old_value;
- gtk_widget_show (window);
-
- gtk_main ();
-
- return 0;
-}
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-</verb></tscreen>
+ xc = GTK_WIDGET(dial)->allocation.width / 2;
+ yc = GTK_WIDGET(dial)->allocation.height / 2;
-<!-- ----------------------------------------------------------------- -->
-<sect1> Creating a widget from scratch.
+ old_value = dial->adjustment->value;
+ dial->angle = atan2(yc-y, x-xc);
-<!-- ----------------------------------------------------------------- -->
-<sect2> Introduction
-<p>
-In this section, we'll learn more about how widgets display themselves
-on the screen and interact with events. As an example of this, we'll
-create an analog dial widget with a pointer that the user can drag to
-set the value.
+ if (dial->angle < -M_PI/2.)
+ dial->angle += 2*M_PI;
-<!-- ----------------------------------------------------------------- -->
-<sect2> Displaying a widget on the screen
-<p>
-There are several steps that are involved in displaying on the screen.
-After the widget is created with a call to <tt/WIDGETNAME_new()/,
-several more functions are needed:
+ if (dial->angle < -M_PI/6)
+ dial->angle = -M_PI/6;
-<itemize>
-<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X
-window for the widget if it has one.
-<item> <tt/WIDGETNAME_map()/ is invoked after the user calls
-<tt/gtk_widget_show()/. It is responsible for making sure the widget
-is actually drawn on the screen (<em/mapped/). For a container class,
-it must also make calls to <tt/map()/> functions of any child widgets.
-<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/
-is called for the widget or one of its ancestors. It makes the actual
-calls to the drawing functions to draw the widget on the screen. For
-container widgets, this function must make calls to
-<tt/gtk_widget_draw()/ for its child widgets.
-<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the
-widget. It makes the necessary calls to the drawing functions to draw
-the exposed portion on the screen. For container widgets, this
-function must generate expose events for its child widgets which don't
-have their own windows. (If they have their own windows, then X will
-generate the necessary expose events)
-</itemize>
+ if (dial->angle > 7.*M_PI/6.)
+ dial->angle = 7.*M_PI/6.;
-You might notice that the last two functions are quite similar - each
-is responsible for drawing the widget on the screen. In fact many
-types of widgets don't really care about the difference between the
-two. The default <tt/draw()/ function in the widget class simply
-generates a synthetic expose event for the redrawn area. However, some
-types of widgets can save work by distinguishing between the two
-functions. For instance, if a widget has multiple X windows, then
-since expose events identify the exposed window, it can redraw only
-the affected window, which is not possible for calls to <tt/draw()/.
+ dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
+ (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
-Container widgets, even if they don't care about the difference for
-themselves, can't simply use the default <tt/draw()/ function because
-their child widgets might care about the difference. However,
-it would be wasteful to duplicate the drawing code between the two
-functions. The convention is that such widgets have a function called
-<tt/WIDGETNAME_paint()/ that does the actual work of drawing the
-widget, that is then called by the <tt/draw()/ and <tt/expose()/
-functions.
+ if (dial->adjustment->value != old_value)
+ {
+ if (dial->policy == GTK_UPDATE_CONTINUOUS)
+ {
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ else
+ {
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
-In our example approach, since the dial widget is not a container
-widget, and only has a single window, we can take the simplest
-approach and use the default <tt/draw()/ function and only implement
-an <tt/expose()/ function.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ {
+ if (dial->timer)
+ gtk_timeout_remove (dial->timer);
-<!-- ----------------------------------------------------------------- -->
-<sect2> The origins of the Dial Widget
-<p>
-Just as all land animals are just variants on the first amphibian that
-crawled up out of the mud, Gtk widgets tend to start off as variants
-of some other, previously written widget. Thus, although this section
-is entilted ``Creating a Widget from Scratch'', the Dial widget really
-began with the source code for the Range widget. This was picked as a
-starting point because it would be nice if our Dial had the same
-interface as the Scale widgets which are just specialized descendents
-of the Range widget. So, though the source code is presented below in
-finished form, it should not be implied that it was written, <em>deus
-ex machina</em> in this fashion. Also, if you aren't yet familiar with
-how scale widgets work from the application writer's point of view, it
-would be a good idea to look them over before continuing.
+ dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
+ (GtkFunction) gtk_dial_timer,
+ (gpointer) dial);
+ }
+ }
+ }
+}
+</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> The Basics
<p>
-Quite a bit of our widget should look pretty familiar from the
-Tictactoe widget. First, we have a header file:
+Changes to the Adjustment by external means are communicated to our
+widget by the ``changed'' and ``value_changed'' signals. The handlers
+for these functions call <tt/gtk_dial_update()/ to validate the
+arguments, compute the new pointer angle, and redraw the widget (by
+calling <tt/gtk_widget_draw()/).
<tscreen><verb>
-/* GTK - The GIMP Toolkit
- * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
+static void
+gtk_dial_update (GtkDial *dial)
+{
+ gfloat new_value;
+
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-#ifndef __GTK_DIAL_H__
-#define __GTK_DIAL_H__
+ new_value = dial->adjustment->value;
+
+ if (new_value < dial->adjustment->lower)
+ new_value = dial->adjustment->lower;
-#include <gdk/gdk.h>
-#include <gtk/gtkadjustment.h>
-#include <gtk/gtkwidget.h>
+ if (new_value > dial->adjustment->upper)
+ new_value = dial->adjustment->upper;
+
+ if (new_value != dial->adjustment->value)
+ {
+ dial->adjustment->value = new_value;
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
+ (dial->adjustment->upper - dial->adjustment->lower);
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
+}
+static void
+gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
-#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
-#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
+ dial = GTK_DIAL (data);
-typedef struct _GtkDial GtkDial;
-typedef struct _GtkDialClass GtkDialClass;
+ if ((dial->old_value != adjustment->value) ||
+ (dial->old_lower != adjustment->lower) ||
+ (dial->old_upper != adjustment->upper))
+ {
+ gtk_dial_update (dial);
+
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ }
+}
-struct _GtkDial
+static void
+gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data)
{
- GtkWidget widget;
+ GtkDial *dial;
- /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
- guint policy : 2;
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
- /* Button currently pressed or 0 if none */
- guint8 button;
+ dial = GTK_DIAL (data);
- /* Dimensions of dial components */
- gint radius;
- gint pointer_width;
+ if (dial->old_value != adjustment->value)
+ {
+ gtk_dial_update (dial);
- /* ID of update timer, or 0 if none */
- guint32 timer;
+ dial->old_value = adjustment->value;
+ }
+}
+</verb></tscreen>
- /* Current angle */
- gfloat angle;
+<!-- ----------------------------------------------------------------- -->
+<sect2> Possible Enhancements
+<p>
- /* Old values from adjustment stored so we know when something changes */
- gfloat old_value;
- gfloat old_lower;
- gfloat old_upper;
+The Dial widget as we've described it so far runs about 670 lines of
+code. Although that might sound like a fair bit, we've really
+accomplished quite a bit with that much code, especially since much of
+that length is headers and boilerplate. However, there are quite a few
+more enhancements that could be made to this widget:
- /* The adjustment object that stores the data for this dial */
- GtkAdjustment *adjustment;
-};
+<itemize>
+<item> If you try this widget out, you'll find that there is some
+flashing as the pointer is dragged around. This is because the entire
+widget is erased every time the pointer is moved before being
+redrawn. Often, the best way to handle this problem is to draw to an
+offscreen pixmap, then copy the final results onto the screen in one
+step. (The ProgressBar widget draws itself in this fashion.)
-struct _GtkDialClass
-{
- GtkWidgetClass parent_class;
-};
+<item> The user should be able to use the up and down arrow keys to
+increase and decrease the value.
+<item> It would be nice if the widget had buttons to increase and
+decrease the value in small or large steps. Although it would be
+possible to use embedded Button widgets for this, we would also like
+the buttons to auto-repeat when held down, as the arrows on a
+scrollbar do. Most of the code to implement this type of behavior can
+be found in the GtkRange widget.
-GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
-guint gtk_dial_get_type (void);
-GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
-void gtk_dial_set_update_policy (GtkDial *dial,
- GtkUpdateType policy);
+<item> The Dial widget could be made into a container widget with a
+single child widget positioned at the bottom between the buttons
+mentioned above. The user could then add their choice of a label or
+entry widget to display the current value of the dial.
-void gtk_dial_set_adjustment (GtkDial *dial,
- GtkAdjustment *adjustment);
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+</itemize>
+<!-- ----------------------------------------------------------------- -->
+<sect1> Learning More
-#endif /* __GTK_DIAL_H__ */
-</verb></tscreen>
+<p>
+Only a small part of the many details involved in creating widgets
+could be described above. If you want to write your own widgets, the
+best source of examples is the GTK source itself. Ask yourself some
+questions about the widget you want to write: is it a Container
+widget? does it have its own window? is it a modification of an
+existing widget? Then find a similar widget, and start making changes.
+Good luck!
-Since there is quite a bit more going on in this widget, than the last
-one, we have more fields in the data structure, but otherwise things
-are pretty similar.
+<!-- ***************************************************************** -->
+<sect>Scribble, A Simple Example Drawing Program
+<!-- ***************************************************************** -->
-Next, after including header files, and declaring a few constants,
-we have some functions to provide information about the widget
-and initialize it:
+<!-- ----------------------------------------------------------------- -->
+<sect1> Overview
-<tscreen><verb>
-#include <math.h>
-#include <stdio.h>
-#include <gtk/gtkmain.h>
-#include <gtk/gtksignal.h>
+<p>
+In this section, we will build a simple drawing program. In the
+process, we will examine how to handle mouse events, how to draw in a
+window, and how to do drawing better by using a backing pixmap. After
+creating the simple drawing program, we will extend it by adding
+support for XInput devices, such as drawing tablets. GTK provides
+support routines which makes getting extended information, such as
+pressure and tilt, from such devices quite easy.
-#include "gtkdial.h"
+<!-- ----------------------------------------------------------------- -->
+<sect1> Event Handling
-#define SCROLL_DELAY_LENGTH 300
-#define DIAL_DEFAULT_SIZE 100
+<p>
+The GTK signals we have already discussed are for high-level actions,
+such as a menu item being selected. However, sometimes it is useful to
+learn about lower-level occurrences, such as the mouse being moved, or
+a key being pressed. There are also GTK signals corresponding to these
+low-level <em>events</em>. The handlers for these signals have an
+extra parameter which is a pointer to a structure containing
+information about the event. For instance, motion events handlers are
+passed a pointer to a GdkEventMotion structure which looks (in part)
+like:
-/* Forward declararations */
+<tscreen><verb>
+struct _GdkEventMotion
+{
+ GdkEventType type;
+ GdkWindow *window;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ ...
+ guint state;
+ ...
+};
+</verb></tscreen>
-[ omitted to save space ]
+<tt/type/ will be set to the event type, in this case
+<tt/GDK_MOTION_NOTIFY/, window is the window in which the event
+occured. <tt/x/ and <tt/y/ give the coordinates of the event,
+and <tt/state/ specifies the modifier state when the event
+occurred (that is, it specifies which modifier keys and mouse buttons
+were pressed.) It is the bitwise OR of some of the following:
-/* Local data */
+<tscreen><verb>
+GDK_SHIFT_MASK
+GDK_LOCK_MASK
+GDK_CONTROL_MASK
+GDK_MOD1_MASK
+GDK_MOD2_MASK
+GDK_MOD3_MASK
+GDK_MOD4_MASK
+GDK_MOD5_MASK
+GDK_BUTTON1_MASK
+GDK_BUTTON2_MASK
+GDK_BUTTON3_MASK
+GDK_BUTTON4_MASK
+GDK_BUTTON5_MASK
+</verb></tscreen>
-static GtkWidgetClass *parent_class = NULL;
+<p>
+As for other signals, to determine what happens when an event occurs
+we call <tt>gtk_signal_connect()</tt>. But we also need let GTK
+know which events we want to be notified about. To do this, we call
+the function:
-guint
-gtk_dial_get_type ()
-{
- static guint dial_type = 0;
+<tscreen><verb>
+void gtk_widget_set_events (GtkWidget *widget,
+ gint events);
+</verb></tscreen>
- if (!dial_type)
- {
- GtkTypeInfo dial_info =
- {
- "GtkDial",
- sizeof (GtkDial),
- sizeof (GtkDialClass),
- (GtkClassInitFunc) gtk_dial_class_init,
- (GtkObjectInitFunc) gtk_dial_init,
- (GtkArgSetFunc) NULL,
- (GtkArgGetFunc) NULL,
- };
+The second field specifies the events we are interested in. It
+is the bitwise OR of constants that specify different types
+of events. For future reference the event types are:
- dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
- }
+<tscreen><verb>
+GDK_EXPOSURE_MASK
+GDK_POINTER_MOTION_MASK
+GDK_POINTER_MOTION_HINT_MASK
+GDK_BUTTON_MOTION_MASK
+GDK_BUTTON1_MOTION_MASK
+GDK_BUTTON2_MOTION_MASK
+GDK_BUTTON3_MOTION_MASK
+GDK_BUTTON_PRESS_MASK
+GDK_BUTTON_RELEASE_MASK
+GDK_KEY_PRESS_MASK
+GDK_KEY_RELEASE_MASK
+GDK_ENTER_NOTIFY_MASK
+GDK_LEAVE_NOTIFY_MASK
+GDK_FOCUS_CHANGE_MASK
+GDK_STRUCTURE_MASK
+GDK_PROPERTY_CHANGE_MASK
+GDK_PROXIMITY_IN_MASK
+GDK_PROXIMITY_OUT_MASK
+</verb></tscreen>
- return dial_type;
-}
+There are a few subtle points that have to be observed when calling
+<tt/gtk_widget_set_events()/. First, it must be called before the X window
+for a GTK widget is created. In practical terms, this means you
+should call it immediately after creating the widget. Second, the
+widget must have an associated X window. For efficiency, many widget
+types do not have their own window, but draw in their parent's window.
+These widgets are:
-static void
-gtk_dial_class_init (GtkDialClass *class)
-{
- GtkObjectClass *object_class;
- GtkWidgetClass *widget_class;
+<tscreen><verb>
+GtkAlignment
+GtkArrow
+GtkBin
+GtkBox
+GtkImage
+GtkItem
+GtkLabel
+GtkPixmap
+GtkScrolledWindow
+GtkSeparator
+GtkTable
+GtkAspectFrame
+GtkFrame
+GtkVBox
+GtkHBox
+GtkVSeparator
+GtkHSeparator
+</verb></tscreen>
- object_class = (GtkObjectClass*) class;
- widget_class = (GtkWidgetClass*) class;
+To capture events for these widgets, you need to use an EventBox
+widget. See the section on
+<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for
+details.
- parent_class = gtk_type_class (gtk_widget_get_type ());
+<p>
+For our drawing program, we want to know when the mouse button is
+pressed and when the mouse is moved, so we specify
+<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also
+want to know when we need to redraw our window, so we specify
+<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a
+Configure event when our window size changes, we don't have to specify
+the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is
+automatically specified for all windows.
- object_class->destroy = gtk_dial_destroy;
+<p>
+It turns out, however, that there is a problem with just specifying
+<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new
+motion event to the event queue every time the user moves the mouse.
+Imagine that it takes us 0.1 seconds to handle a motion event, but the
+X server queues a new motion event every 0.05 seconds. We will soon
+get way behind the users drawing. If the user draws for 5 seconds,
+it will take us another 5 seconds to catch up after they release
+the mouse button! What we would like is to only get one motion
+event for each event we process. The way to do this is to
+specify <tt/GDK_POINTER_MOTION_HINT_MASK/.
- widget_class->realize = gtk_dial_realize;
- widget_class->expose_event = gtk_dial_expose;
- widget_class->size_request = gtk_dial_size_request;
- widget_class->size_allocate = gtk_dial_size_allocate;
- widget_class->button_press_event = gtk_dial_button_press;
- widget_class->button_release_event = gtk_dial_button_release;
- widget_class->motion_notify_event = gtk_dial_motion_notify;
-}
+<p>
+When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends
+us a motion event the first time the pointer moves after entering
+our window, or after a button press or release event. Subsequent
+motion events will be suppressed until we explicitely ask for
+the position of the pointer using the function:
-static void
-gtk_dial_init (GtkDial *dial)
-{
- dial->button = 0;
- dial->policy = GTK_UPDATE_CONTINUOUS;
- dial->timer = 0;
- dial->radius = 0;
- dial->pointer_width = 0;
- dial->angle = 0.0;
- dial->old_value = 0.0;
- dial->old_lower = 0.0;
- dial->old_upper = 0.0;
- dial->adjustment = NULL;
-}
+<tscreen><verb>
+GdkWindow* gdk_window_get_pointer (GdkWindow *window,
+ gint *x,
+ gint *y,
+ GdkModifierType *mask);
+</verb></tscreen>
-GtkWidget*
-gtk_dial_new (GtkAdjustment *adjustment)
-{
- GtkDial *dial;
+(There is another function, <tt>gtk_widget_get_pointer()</tt> which
+has a simpler interface, but turns out not to be very useful, since
+it only retrieves the position of the mouse, not whether the buttons
+are pressed.)
- dial = gtk_type_new (gtk_dial_get_type ());
+<p>
+The code to set the events for our window then looks like:
- if (!adjustment)
- adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+<tscreen><verb>
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
+ (GtkSignalFunc) expose_event, NULL);
+ gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
+ (GtkSignalFunc) configure_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
+ (GtkSignalFunc) motion_notify_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
+ (GtkSignalFunc) button_press_event, NULL);
- gtk_dial_set_adjustment (dial, adjustment);
+ gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK);
+</verb></tscreen>
- return GTK_WIDGET (dial);
-}
+We'll save the "expose_event" and "configure_event" handlers for
+later. The "motion_notify_event" and "button_press_event" handlers
+pretty simple:
-static void
-gtk_dial_destroy (GtkObject *object)
+<tscreen><verb>
+static gint
+button_press_event (GtkWidget *widget, GdkEventButton *event)
{
- GtkDial *dial;
-
- g_return_if_fail (object != NULL);
- g_return_if_fail (GTK_IS_DIAL (object));
+ if (event->button == 1 && pixmap != NULL)
+ draw_brush (widget, event->x, event->y);
- dial = GTK_DIAL (object);
+ return TRUE;
+}
- if (dial->adjustment)
- gtk_object_unref (GTK_OBJECT (dial->adjustment));
+static gint
+motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
+{
+ int x, y;
+ GdkModifierType state;
- if (GTK_OBJECT_CLASS (parent_class)->destroy)
- (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+ if (event->is_hint)
+ gdk_window_get_pointer (event->window, &x, &y, &state);
+ else
+ {
+ x = event->x;
+ y = event->y;
+ state = event->state;
+ }
+
+ if (state & GDK_BUTTON1_MASK && pixmap != NULL)
+ draw_brush (widget, x, y);
+
+ return TRUE;
}
</verb></tscreen>
-Note that this <tt/init()/ function does less than for the Tictactoe
-widget, since this is not a composite widget, and the <tt/new()/
-function does more, since it now has an argument. Also, note that when
-we store a pointer to the Adjustment object, we increment its
-reference count, (and correspondingly decrement when we no longer use
-it) so that GTK can keep track of when it can be safely destroyed.
+<!-- ----------------------------------------------------------------- -->
+<sect1> The DrawingArea Widget, And Drawing
<p>
-Also, there are a few function to manipulate the widget's options:
+We know turn to the process of drawing on the screen. The
+widget we use for this is the DrawingArea widget. A drawing area
+widget is essentially an X window and nothing more. It is a blank
+canvas in which we can draw whatever we like. A drawing area
+is created using the call:
<tscreen><verb>
-GtkAdjustment*
-gtk_dial_get_adjustment (GtkDial *dial)
-{
- g_return_val_if_fail (dial != NULL, NULL);
- g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-
- return dial->adjustment;
-}
-
-void
-gtk_dial_set_update_policy (GtkDial *dial,
- GtkUpdateType policy)
-{
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
+GtkWidget* gtk_drawing_area_new (void);
+</verb></tscreen>
- dial->policy = policy;
-}
+A default size for the widget can be specified by calling:
-void
-gtk_dial_set_adjustment (GtkDial *dial,
- GtkAdjustment *adjustment)
-{
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
+<tscreen><verb>
+void gtk_drawing_area_size (GtkDrawingArea *darea,
+ gint width,
+ gint height);
+</verb></tscreen>
- if (dial->adjustment)
- {
- gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
- gtk_object_unref (GTK_OBJECT (dial->adjustment));
- }
+This default size can be overriden, as is true for all widgets,
+by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can
+be overridden if the user manually resizes the the window containing
+the drawing area.
- dial->adjustment = adjustment;
- gtk_object_ref (GTK_OBJECT (dial->adjustment));
+<p>
+It should be noted that when we create a DrawingArea widget, we are,
+<em>completely</em> responsible for drawing the contents. If our
+window is obscured then uncovered, we get an exposure event and must
+redraw what was previously hidden.
- gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
- (GtkSignalFunc) gtk_dial_adjustment_changed,
- (gpointer) dial);
- gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
- (GtkSignalFunc) gtk_dial_adjustment_value_changed,
- (gpointer) dial);
+<p>
+Having to remember everything that was drawn on the screen so we
+can properly redraw it can, to say the least, be a nuisance. In
+addition, it can be visually distracting if portions of the
+window are cleared, then redrawn step by step. The solution to
+this problem is to use an offscreen <em>backing pixmap</em>.
+Instead of drawing directly to the screen, we draw to an image
+stored in server memory but not displayed, then when the image
+changes or new portions of the image are displayed, we copy the
+relevant portions onto the screen.
- dial->old_value = adjustment->value;
- dial->old_lower = adjustment->lower;
- dial->old_upper = adjustment->upper;
+<p>
+To create an offscreen pixmap, we call the function:
- gtk_dial_update (dial);
-}
+<tscreen><verb>
+GdkPixmap* gdk_pixmap_new (GdkWindow *window,
+ gint width,
+ gint height,
+ gint depth);
</verb></tscreen>
-<sect2> <tt/gtk_dial_realize()/
+The <tt>window</tt> parameter specifies a GDK window that this pixmap
+takes some of its properties from. <tt>width</tt> and <tt>height</tt>
+specify the size of the pixmap. <tt>depth</tt> specifies the <em>color
+depth</em>, that is the number of bits per pixel, for the new window.
+If the depth is specified as <tt>-1</tt>, it will match the depth
+of <tt>window</tt>.
<p>
-Now we come to some new types of functions. First, we have a function
-that does the work of creating the X window. Notice that a mask is
-passed to the function <tt/gdk_window_new()/ which specifies which fields of
-the GdkWindowAttr structure actually have data in them (the remaining
-fields wll be given default values). Also worth noting is the way the
-event mask of the widget is created. We call
-<tt/gtk_widget_get_events()/ to retrieve the event mask that the user
-has specified for this widget (with <tt/gtk_widget_set_events()/, and
-add the events that we are interested in ourselves.
+We create the pixmap in our "configure_event" handler. This event
+is generated whenever the window changes size, including when it
+is originally created.
+
+<tscreen><verb>
+/* Backing pixmap for drawing area */
+static GdkPixmap *pixmap = NULL;
+
+/* Create a new backing pixmap of the appropriate size */
+static gint
+configure_event (GtkWidget *widget, GdkEventConfigure *event)
+{
+ if (pixmap)
+ gdk_pixmap_unref(pixmap);
+
+ pixmap = gdk_pixmap_new(widget->window,
+ widget->allocation.width,
+ widget->allocation.height,
+ -1);
+ gdk_draw_rectangle (pixmap,
+ widget->style->white_gc,
+ TRUE,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+
+ return TRUE;
+}
+</verb></tscreen>
+
+The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap
+initially to white. We'll say more about that in a moment.
<p>
-After creating the window, we set its style and background, and put a
-pointer to the widget in the user data field of the GdkWindow. This
-last step allows GTK to dispatch events for this window to the correct
-widget.
+Our exposure event handler then simply copies the relevant portion
+of the pixmap onto the screen (we determine the area we need
+to redraw by using the event->area field of the exposure event):
<tscreen><verb>
-static void
-gtk_dial_realize (GtkWidget *widget)
+/* Redraw the screen from the backing pixmap */
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *event)
{
- GtkDial *dial;
- GdkWindowAttr attributes;
- gint attributes_mask;
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
- g_return_if_fail (widget != NULL);
- g_return_if_fail (GTK_IS_DIAL (widget));
+ return FALSE;
+}
+</verb></tscreen>
- GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
- dial = GTK_DIAL (widget);
+We've now seen how to keep the screen up to date with our pixmap, but
+how do we actually draw interesting stuff on our pixmap? There are a
+large number of calls in GTK's GDK library for drawing on
+<em>drawables</em>. A drawable is simply something that can be drawn
+upon. It can be a window, a pixmap, or a bitmap (a black and white
+image). We've already seen two such calls above,
+<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The
+complete list is:
- attributes.x = widget->allocation.x;
- attributes.y = widget->allocation.y;
- attributes.width = widget->allocation.width;
- attributes.height = widget->allocation.height;
- attributes.wclass = GDK_INPUT_OUTPUT;
- attributes.window_type = GDK_WINDOW_CHILD;
- attributes.event_mask = gtk_widget_get_events (widget) |
- GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
- GDK_POINTER_MOTION_HINT_MASK;
- attributes.visual = gtk_widget_get_visual (widget);
- attributes.colormap = gtk_widget_get_colormap (widget);
+<tscreen><verb>
+gdk_draw_line ()
+gdk_draw_rectangle ()
+gdk_draw_arc ()
+gdk_draw_polygon ()
+gdk_draw_string ()
+gdk_draw_text ()
+gdk_draw_pixmap ()
+gdk_draw_bitmap ()
+gdk_draw_image ()
+gdk_draw_points ()
+gdk_draw_segments ()
+</verb></tscreen>
- attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
- widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
+See the reference documentation or the header file
+<tt><gdk/gdk.h></tt> for further details on these functions.
+These functions all share the same first two arguments. The first
+argument is the drawable to draw upon, the second argument is a
+<em>graphics context</em> (GC).
- widget->style = gtk_style_attach (widget->style, widget->window);
+<p>
+A graphics context encapsulates information about things such as
+foreground and background color and line width. GDK has a full set of
+functions for creating and modifying graphics contexts, but to keep
+things simple we'll just use predefined graphics contexts. Each widget
+has an associated style. (Which can be modified in a gtkrc file, see
+the section GTK's rc file.) This, among other things, stores a number
+of graphics contexts. Some examples of accessing these graphics
+contexts are:
- gdk_window_set_user_data (widget->window, widget);
+<tscreen><verb>
+widget->style->white_gc
+widget->style->black_gc
+widget->style->fg_gc[GTK_STATE_NORMAL]
+widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
+</verb></tscreen>
- gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
-}
+The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and
+<tt>light_gc</tt> are indexed by a parameter of type
+<tt>GtkStateType</tt> which can take on the values:
+
+<tscreen><verb>
+GTK_STATE_NORMAL,
+GTK_STATE_ACTIVE,
+GTK_STATE_PRELIGHT,
+GTK_STATE_SELECTED,
+GTK_STATE_INSENSITIVE
</verb></tscreen>
-<sect2> Size negotiation
+For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground
+color is white and the default background color, dark blue.
<p>
-Before the first time that the window containing a widget is
-displayed, and whenever the layout of the window changes, GTK asks
-each child widget for its desired size. This request is handled by the
-function, <tt/gtk_dial_size_request()/. Since our widget isn't a
-container widget, and has no real constraints on its size, we just
-return a reasonable default value.
+Our function <tt>draw_brush()</tt>, which does the actual drawing
+on the screen, is then:
<tscreen><verb>
-static void
-gtk_dial_size_request (GtkWidget *widget,
- GtkRequisition *requisition)
+/* Draw a rectangle on the screen */
+static void
+draw_brush (GtkWidget *widget, gdouble x, gdouble y)
{
- requisition->width = DIAL_DEFAULT_SIZE;
- requisition->height = DIAL_DEFAULT_SIZE;
+ GdkRectangle update_rect;
+
+ update_rect.x = x - 5;
+ update_rect.y = y - 5;
+ update_rect.width = 10;
+ update_rect.height = 10;
+ gdk_draw_rectangle (pixmap,
+ widget->style->black_gc,
+ TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
}
</verb></tscreen>
-<p>
-After all the widgets have requested an ideal size, the layout of the
-window is computed and each child widget is notified of its actual
-size. Usually, this will at least as large as the requested size, but
-if for instance, the user has resized the window, it may occasionally
-be smaller than the requested size. The size notification is handled
-by the function <tt/gtk_dial_size_allocate()/. Notice that as well as
-computing the sizes of some component pieces for future use, this
-routine also does the grunt work of moving the widgets X window into
-the new position and size.
+After we draw the rectangle representing the brush onto the pixmap,
+we call the function:
<tscreen><verb>
-static void
-gtk_dial_size_allocate (GtkWidget *widget,
- GtkAllocation *allocation)
-{
- GtkDial *dial;
+void gtk_widget_draw (GtkWidget *widget,
+ GdkRectangle *area);
+</verb></tscreen>
- g_return_if_fail (widget != NULL);
- g_return_if_fail (GTK_IS_DIAL (widget));
- g_return_if_fail (allocation != NULL);
+which notifies X that the area given by the <tt>area</tt> parameter
+needs to be updated. X will eventually generate an expose event
+(possibly combining the areas passed in several calls to
+<tt>gtk_widget_draw()</tt>) which will cause our expose event handler
+to copy the relevant portions to the screen.
- widget->allocation = *allocation;
- if (GTK_WIDGET_REALIZED (widget))
- {
- dial = GTK_DIAL (widget);
+<p>
+We have now covered the entire drawing program except for a few
+mundane details like creating the main window. The complete
+source code is available from the location from which you got
+this tutorial, or from:
- gdk_window_move_resize (widget->window,
- allocation->x, allocation->y,
- allocation->width, allocation->height);
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
- dial->radius = MAX(allocation->width,allocation->height) * 0.45;
- dial->pointer_width = dial->radius / 5;
- }
-}
-</verb></tscreen>.
<!-- ----------------------------------------------------------------- -->
-<sect2> <tt/gtk_dial_expose()/
+<sect1> Adding XInput support
<p>
-As mentioned above, all the drawing of this widget is done in the
-handler for expose events. There's not much to remark on here except
-the use of the function <tt/gtk_draw_polygon/ to draw the pointer with
-three dimensional shading according to the colors stored in the
-widget's style.
-
-<tscreen><verb>
-static gint
-gtk_dial_expose (GtkWidget *widget,
- GdkEventExpose *event)
-{
- GtkDial *dial;
- GdkPoint points[3];
- gdouble s,c;
- gdouble theta;
- gint xc, yc;
- gint tick_length;
- gint i;
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
-
- if (event->count > 0)
- return FALSE;
-
- dial = GTK_DIAL (widget);
+It is now possible to buy quite inexpensive input devices such
+as drawing tablets, which allow drawing with a much greater
+ease of artistic expression than does a mouse. The simplest way
+to use such devices is simply as a replacement for the mouse,
+but that misses out many of the advantages of these devices,
+such as:
- gdk_window_clear_area (widget->window,
- 0, 0,
- widget->allocation.width,
- widget->allocation.height);
+<itemize>
+<item> Pressure sensitivity
+<item> Tilt reporting
+<item> Sub-pixel positioning
+<item> Multiple inputs (for example, a stylus with a point and eraser)
+</itemize>
- xc = widget->allocation.width/2;
- yc = widget->allocation.height/2;
+For information about the XInput extension, see the <htmlurl
+url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
+name="XInput-HOWTO">.
- /* Draw ticks */
+<p>
+If we examine the full definition of, for example, the GdkEventMotion
+structure, we see that it has fields to support extended device
+information.
- for (i=0; i<25; i++)
- {
- theta = (i*M_PI/18. - M_PI/6.);
- s = sin(theta);
- c = cos(theta);
+<tscreen><verb>
+struct _GdkEventMotion
+{
+ GdkEventType type;
+ GdkWindow *window;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ gdouble pressure;
+ gdouble xtilt;
+ gdouble ytilt;
+ guint state;
+ gint16 is_hint;
+ GdkInputSource source;
+ guint32 deviceid;
+};
+</verb></tscreen>
- tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
-
- gdk_draw_line (widget->window,
- widget->style->fg_gc[widget->state],
- xc + c*(dial->radius - tick_length),
- yc - s*(dial->radius - tick_length),
- xc + c*dial->radius,
- yc - s*dial->radius);
- }
+<tt/pressure/ gives the pressure as a floating point number between
+0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between
+-1 and 1, corresponding to the degree of tilt in each direction.
+<tt/source/ and <tt/deviceid/ specify the device for which the
+event occurred in two different ways. <tt/source/ gives some simple
+information about the type of device. It can take the enumeration
+values.
- /* Draw pointer */
+<tscreen><verb>
+GDK_SOURCE_MOUSE
+GDK_SOURCE_PEN
+GDK_SOURCE_ERASER
+GDK_SOURCE_CURSOR
+</verb></tscreen>
- s = sin(dial->angle);
- c = cos(dial->angle);
+<tt/deviceid/ specifies a unique numeric ID for the device. This can
+be used to find out further information about the device using the
+<tt/gdk_input_list_devices()/ call (see below). The special value
+<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually
+the mouse.)
+<sect2> Enabling extended device information
- points[0].x = xc + s*dial->pointer_width/2;
- points[0].y = yc + c*dial->pointer_width/2;
- points[1].x = xc + c*dial->radius;
- points[1].y = yc - s*dial->radius;
- points[2].x = xc - s*dial->pointer_width/2;
- points[2].y = yc - c*dial->pointer_width/2;
+<p>
+To let GTK know about our interest in the extended device information,
+we merely have to add a single line to our program:
- gtk_draw_polygon (widget->style,
- widget->window,
- GTK_STATE_NORMAL,
- GTK_SHADOW_OUT,
- points, 3,
- TRUE);
-
- return FALSE;
-}
+<tscreen><verb>
+gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
</verb></tscreen>
-<!-- ----------------------------------------------------------------- -->
-<sect2> Event handling
-
-<p>
-
-The rest of the widget's code handles various types of events, and
-isn't too different from what would be found in many GTK
-applications. Two types of events can occur - either the user can
-click on the widget with the mouse and drag to move the pointer, or
-the value of the Adjustment object can change due to some external
-circumstance.
+By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that
+we are interested in extension events, but only if we don't have
+to draw our own cursor. See the section <ref
+id="sec_Further_Sophistications" name="Further Sophistications"> below
+for more information about drawing the cursor. We could also
+give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing
+to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert
+back to the default condition.
<p>
-When the user clicks on the widget, we check to see if the click was
-appropriately near the pointer, and if so, store then button that the
-user clicked with in the <tt/button/ field of the widget
-structure, and grab all mouse events with a call to
-<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the
-value of the control to be recomputed (by the function
-<tt/gtk_dial_update_mouse/). Depending on the policy that has been
-set, "value_changed" events are either generated instantly
-(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with
-<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the
-button is released (<tt/GTK_UPDATE_DISCONTINUOUS/).
+This is not completely the end of the story however. By default,
+no extension devices are enabled. We need a mechanism to allow
+users to enable and configure their extension devices. GTK provides
+the InputDialog widget to automate this process. The following
+procedure manages an InputDialog widget. It creates the dialog if
+it isn't present, and raises it to the top otherwise.
<tscreen><verb>
-static gint
-gtk_dial_button_press (GtkWidget *widget,
- GdkEventButton *event)
+void
+input_dialog_destroy (GtkWidget *w, gpointer data)
{
- GtkDial *dial;
- gint dx, dy;
- double s, c;
- double d_parallel;
- double d_perpendicular;
-
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
+ *((GtkWidget **)data) = NULL;
+}
- dial = GTK_DIAL (widget);
+void
+create_input_dialog ()
+{
+ static GtkWidget *inputd = NULL;
- /* Determine if button press was within pointer region - we
- do this by computing the parallel and perpendicular distance of
- the point where the mouse was pressed from the line passing through
- the pointer */
-
- dx = event->x - widget->allocation.width / 2;
- dy = widget->allocation.height / 2 - event->y;
-
- s = sin(dial->angle);
- c = cos(dial->angle);
-
- d_parallel = s*dy + c*dx;
- d_perpendicular = fabs(s*dx - c*dy);
-
- if (!dial->button &&
- (d_perpendicular < dial->pointer_width/2) &&
- (d_parallel > - dial->pointer_width))
+ if (!inputd)
{
- gtk_grab_add (widget);
+ inputd = gtk_input_dialog_new();
- dial->button = event->button;
+ gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
+ (GtkSignalFunc)input_dialog_destroy, &inputd);
+ gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
+ "clicked",
+ (GtkSignalFunc)gtk_widget_hide,
+ GTK_OBJECT(inputd));
+ gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
- gtk_dial_update_mouse (dial, event->x, event->y);
+ gtk_widget_show (inputd);
+ }
+ else
+ {
+ if (!GTK_WIDGET_MAPPED(inputd))
+ gtk_widget_show(inputd);
+ else
+ gdk_window_raise(inputd->window);
}
-
- return FALSE;
}
+</verb></tscreen>
-static gint
-gtk_dial_button_release (GtkWidget *widget,
- GdkEventButton *event)
-{
- GtkDial *dial;
+(You might want to take note of the way we handle this dialog. By
+connecting to the "destroy" signal, we make sure that we don't keep a
+pointer to dialog around after it is destroyed - that could lead to a
+segfault.)
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
+<p>
+The InputDialog has two buttons "Close" and "Save", which by default
+have no actions assigned to them. In the above function we make
+"Close" hide the dialog, hide the "Save" button, since we don't
+implement saving of XInput options in this program.
- dial = GTK_DIAL (widget);
+<sect2> Using extended device information
- if (dial->button == event->button)
- {
- gtk_grab_remove (widget);
+<p>
+Once we've enabled the device, we can just use the extended
+device information in the extra fields of the event structures.
+In fact, it is always safe to use this information since these
+fields will have reasonable default values even when extended
+events are not enabled.
- dial->button = 0;
+<p>
+Once change we do have to make is to call
+<tt/gdk_input_window_get_pointer()/ instead of
+<tt/gdk_window_get_pointer/. This is necessary because
+<tt/gdk_window_get_pointer/ doesn't return the extended device
+information.
- if (dial->policy == GTK_UPDATE_DELAYED)
- gtk_timeout_remove (dial->timer);
-
- if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
- (dial->old_value != dial->adjustment->value))
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
+<tscreen><verb>
+void gdk_input_window_get_pointer (GdkWindow *window,
+ guint32 deviceid,
+ gdouble *x,
+ gdouble *y,
+ gdouble *pressure,
+ gdouble *xtilt,
+ gdouble *ytilt,
+ GdkModifierType *mask);
+</verb></tscreen>
- return FALSE;
-}
+When calling this function, we need to specify the device ID as
+well as the window. Usually, we'll get the device ID from the
+<tt/deviceid/ field of an event structure. Again, this function
+will return reasonable values when extension events are not
+enabled. (In this case, <tt/event->deviceid/ will have the value
+<tt/GDK_CORE_POINTER/).
+So the basic structure of our button-press and motion event handlers,
+doesn't change much - we just need to add code to deal with the
+extended information.
+
+<tscreen><verb>
static gint
-gtk_dial_motion_notify (GtkWidget *widget,
- GdkEventMotion *event)
+button_press_event (GtkWidget *widget, GdkEventButton *event)
{
- GtkDial *dial;
- GdkModifierType mods;
- gint x, y, mask;
-
- g_return_val_if_fail (widget != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
- g_return_val_if_fail (event != NULL, FALSE);
-
- dial = GTK_DIAL (widget);
-
- if (dial->button != 0)
- {
- x = event->x;
- y = event->y;
-
- if (event->is_hint || (event->window != widget->window))
- gdk_window_get_pointer (widget->window, &x, &y, &mods);
-
- switch (dial->button)
- {
- case 1:
- mask = GDK_BUTTON1_MASK;
- break;
- case 2:
- mask = GDK_BUTTON2_MASK;
- break;
- case 3:
- mask = GDK_BUTTON3_MASK;
- break;
- default:
- mask = 0;
- break;
- }
-
- if (mods & mask)
- gtk_dial_update_mouse (dial, x,y);
- }
+ print_button_press (event->deviceid);
+
+ if (event->button == 1 && pixmap != NULL)
+ draw_brush (widget, event->source, event->x, event->y, event->pressure);
- return FALSE;
+ return TRUE;
}
static gint
-gtk_dial_timer (GtkDial *dial)
+motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
- g_return_val_if_fail (dial != NULL, FALSE);
- g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
-
- if (dial->policy == GTK_UPDATE_DELAYED)
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ gdouble x, y;
+ gdouble pressure;
+ GdkModifierType state;
- return FALSE;
+ if (event->is_hint)
+ gdk_input_window_get_pointer (event->window, event->deviceid,
+ &x, &y, &pressure, NULL, NULL, &state);
+ else
+ {
+ x = event->x;
+ y = event->y;
+ pressure = event->pressure;
+ state = event->state;
+ }
+
+ if (state & GDK_BUTTON1_MASK && pixmap != NULL)
+ draw_brush (widget, event->source, x, y, pressure);
+
+ return TRUE;
}
+</verb></tscreen>
+
+We also need to do something with the new information. Our new
+<tt/draw_brush()/ function draws with a different color for
+each <tt/event->source/ and changes the brush size depending
+on the pressure.
+<tscreen><verb>
+/* Draw a rectangle on the screen, size depending on pressure,
+ and color on the type of device */
static void
-gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+draw_brush (GtkWidget *widget, GdkInputSource source,
+ gdouble x, gdouble y, gdouble pressure)
{
- gint xc, yc;
- gfloat old_value;
-
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
-
- xc = GTK_WIDGET(dial)->allocation.width / 2;
- yc = GTK_WIDGET(dial)->allocation.height / 2;
-
- old_value = dial->adjustment->value;
- dial->angle = atan2(yc-y, x-xc);
-
- if (dial->angle < -M_PI/2.)
- dial->angle += 2*M_PI;
-
- if (dial->angle < -M_PI/6)
- dial->angle = -M_PI/6;
-
- if (dial->angle > 7.*M_PI/6.)
- dial->angle = 7.*M_PI/6.;
-
- dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
- (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
+ GdkGC *gc;
+ GdkRectangle update_rect;
- if (dial->adjustment->value != old_value)
+ switch (source)
{
- if (dial->policy == GTK_UPDATE_CONTINUOUS)
- {
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
- else
- {
- gtk_widget_draw (GTK_WIDGET(dial), NULL);
-
- if (dial->policy == GTK_UPDATE_DELAYED)
- {
- if (dial->timer)
- gtk_timeout_remove (dial->timer);
-
- dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
- (GtkFunction) gtk_dial_timer,
- (gpointer) dial);
- }
- }
+ case GDK_SOURCE_MOUSE:
+ gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
+ break;
+ case GDK_SOURCE_PEN:
+ gc = widget->style->black_gc;
+ break;
+ case GDK_SOURCE_ERASER:
+ gc = widget->style->white_gc;
+ break;
+ default:
+ gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
}
+
+ update_rect.x = x - 10 * pressure;
+ update_rect.y = y - 10 * pressure;
+ update_rect.width = 20 * pressure;
+ update_rect.height = 20 * pressure;
+ gdk_draw_rectangle (pixmap, gc, TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
}
</verb></tscreen>
+<sect2> Finding out more about a device
+
<p>
-Changes to the Adjustment by external means are communicated to our
-widget by the ``changed'' and ``value_changed'' signals. The handlers
-for these functions call <tt/gtk_dial_update()/ to validate the
-arguments, compute the new pointer angle, and redraw the widget (by
-calling <tt/gtk_widget_draw()/).
+As an example of how to find out more about a device, our program
+will print the name of the device that generates each button
+press. To find out the name of a device, we call the function:
<tscreen><verb>
-static void
-gtk_dial_update (GtkDial *dial)
-{
- gfloat new_value;
-
- g_return_if_fail (dial != NULL);
- g_return_if_fail (GTK_IS_DIAL (dial));
-
- new_value = dial->adjustment->value;
-
- if (new_value < dial->adjustment->lower)
- new_value = dial->adjustment->lower;
+GList *gdk_input_list_devices (void);
+</verb></tscreen>
- if (new_value > dial->adjustment->upper)
- new_value = dial->adjustment->upper;
+which returns a GList (a linked list type from the glib library)
+of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined
+as:
- if (new_value != dial->adjustment->value)
- {
- dial->adjustment->value = new_value;
- gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
- }
+<tscreen><verb>
+struct _GdkDeviceInfo
+{
+ guint32 deviceid;
+ gchar *name;
+ GdkInputSource source;
+ GdkInputMode mode;
+ gint has_cursor;
+ gint num_axes;
+ GdkAxisUse *axes;
+ gint num_keys;
+ GdkDeviceKey *keys;
+};
+</verb></tscreen>
- dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
- (dial->adjustment->upper - dial->adjustment->lower);
+Most of these fields are configuration information that you
+can ignore unless you are implemented XInput configuration
+saving. The we are interested in here is <tt/name/ which is
+simply the name that X assigns to the device. The other field
+that isn't configuration information is <tt/has_cursor/. If
+<tt/has_cursor/ is false, then we we need to draw our own
+cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/,
+we don't have to worry about this.
- gtk_widget_draw (GTK_WIDGET(dial), NULL);
-}
+<p>
+Our <tt/print_button_press()/ function simply iterates through
+the returned list until it finds a match, then prints out
+the name of the device.
+<tscreen><verb>
static void
-gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
- gpointer data)
+print_button_press (guint32 deviceid)
{
- GtkDial *dial;
-
- g_return_if_fail (adjustment != NULL);
- g_return_if_fail (data != NULL);
+ GList *tmp_list;
- dial = GTK_DIAL (data);
+ /* gdk_input_list_devices returns an internal list, so we shouldn't
+ free it afterwards */
+ tmp_list = gdk_input_list_devices();
- if ((dial->old_value != adjustment->value) ||
- (dial->old_lower != adjustment->lower) ||
- (dial->old_upper != adjustment->upper))
+ while (tmp_list)
{
- gtk_dial_update (dial);
+ GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
- dial->old_value = adjustment->value;
- dial->old_lower = adjustment->lower;
- dial->old_upper = adjustment->upper;
+ if (info->deviceid == deviceid)
+ {
+ printf("Button press on device '%s'\n", info->name);
+ return;
+ }
+
+ tmp_list = tmp_list->next;
}
}
+</verb></tscreen>
-static void
-gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
- gpointer data)
-{
- GtkDial *dial;
+That completes the changes to ``XInputize'' our program. As with
+the first version, the complete source is available at the location
+from which you got this tutorial, or from:
- g_return_if_fail (adjustment != NULL);
- g_return_if_fail (data != NULL);
+<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
+name="http://www.gtk.org/~otaylor/gtk/tutorial/">
- dial = GTK_DIAL (data);
- if (dial->old_value != adjustment->value)
- {
- gtk_dial_update (dial);
+<sect2> Further sophistications <label id="sec_Further_Sophistications">
- dial->old_value = adjustment->value;
- }
-}
-</verb></tscreen>
+<p>
+Although our program now supports XInput quite well, it lacks some
+features we would want in a full-featured application. First, the user
+probably doesn't want to have to configure their device each time they
+run the program, so we should allow them to save the device
+configuration. This is done by iterating through the return of
+<tt/gdk_input_list_devices()/ and writing out the configuration to a
+file.
-<!-- ----------------------------------------------------------------- -->
-<sect2> Possible Enhancements
<p>
+To restore the state next time the program is run, GDK provides
+functions to change device configuration:
-The Dial widget as we've described it so far runs about 670 lines of
-code. Although that might sound like a fair bit, we've really
-accomplished quite a bit with that much code, especially since much of
-that length is headers and boilerplate. However, there are quite a few
-more enhancements that could be made to this widget:
+<tscreen><verb>
+gdk_input_set_extension_events()
+gdk_input_set_source()
+gdk_input_set_mode()
+gdk_input_set_axes()
+gdk_input_set_key()
+</verb></tscreen>
-<itemize>
-<item> If you try this widget out, you'll find that there is some
-flashing as the pointer is dragged around. This is because the entire
-widget is erased every time the pointer is moved before being
-redrawn. Often, the best way to handle this problem is to draw to an
-offscreen pixmap, then copy the final results onto the screen in one
-step. (The ProgressBar widget draws itself in this fashion.)
+(The list returned from <tt/gdk_input_list_devices()/ should not be
+modified directly.) An example of doing this can be found in the
+drawing program gsumi. (Available from <htmlurl
+url="http://www.msc.cornell.edu/~otaylor/gsumi/"
+name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it
+would be nice to have a standard way of doing this for all
+applications. This probably belongs at a slightly higher level than
+GTK, perhaps in the GNOME library.
-<item> The user should be able to use the up and down arrow keys to
-increase and decrease the value.
+<p>
+Another major ommission that we have mentioned above is the lack of
+cursor drawing. Platforms other than XFree86 currently do not allow
+simultaneously using a device as both the core pointer and directly by
+an application. See the <url
+url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
+name="XInput-HOWTO"> for more information about this. This means that
+applications that want to support the widest audience need to draw
+their own cursor.
-<item> It would be nice if the widget had buttons to increase and
-decrease the value in small or large steps. Although it would be
-possible to use embedded Button widgets for this, we would also like
-the buttons to auto-repeat when held down, as the arrows on a
-scrollbar do. Most of the code to implement this type of behavior can
-be found in the GtkRange widget.
+<p>
+An application that draws it's own cursor needs to do two things:
+determine if the current device needs a cursor drawn or not, and
+determine if the current device is in proximity. (If the current
+device is a drawing tablet, it's a nice touch to make the cursor
+disappear when the stylus is lifted from the tablet. When the
+device is touching the stylus, that is called "in proximity.")
+The first is done by searching the device list, as we did
+to find out the device name. The second is achieved by selecting
+"proximity_out" events. An example of drawing one's own cursor is
+found in the 'testinput' program found in the GTK distribution.
-<item> The Dial widget could be made into a container widget with a
-single child widget positioned at the bottom between the buttons
-mentioned above. The user could then add their choice of a label or
-entry widget to display the current value of the dial.
+<!-- ***************************************************************** -->
+<sect>Tips For Writing GTK Applications
+<!-- ***************************************************************** -->
-</itemize>
+<p>
+This section is simply a gathering of wisdom, general style guidelines and hints to
+creating good GTK applications. It is totally useless right now cause it's
+only a topic sentence :)
-<!-- ----------------------------------------------------------------- -->
-<sect1> Learning More
+Use GNU autoconf and automake! They are your friends :) I am planning to
+make a quick intro on them here.
+
+<!-- ***************************************************************** -->
+<sect>Contributing
+<!-- ***************************************************************** -->
<p>
-Only a small part of the many details involved in creating widgets
-could be described above. If you want to write your own widgets, the
-best source of examples is the GTK source itself. Ask yourself some
-questions about the widget you want to write: is it a Container
-widget? does it have its own window? is it a modification of an
-existing widget? Then find a similar widget, and start making changes.
-Good luck!
+This document, like so much other great software out there, was created for
+free by volunteers. If you are at all knowledgeable about any aspect of GTK
+that does not already have documentation, please consider contributing to
+this document.
+<p>
+If you do decide to contribute, please mail your text to Tony Gale,
+<tt><htmlurl url="mailto:gale@gtk.org"
+name="gale@gtk.org"></tt>. Also, be aware that the entirety of this
+document is free, and any addition by yourself must also be free. That is,
+people may use any portion of your examples in their programs, and copies
+of this document may be distributed at will etc.
+<p>
+Thank you.
<!-- ***************************************************************** -->
-<sect>Scribble, A Simple Example Drawing Program
+<sect>Credits
<!-- ***************************************************************** -->
+<p>
+I would like to thank the following for their contributions to this text.
-<!-- ----------------------------------------------------------------- -->
-<sect1> Overview
+<itemize>
+<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
+name="chamele0n@geocities.com"></tt> for the menus tutorial.
-<p>
-In this section, we will build a simple drawing program. In the
-process, we will examine how to handle mouse events, how to draw in a
-window, and how to do drawing better by using a backing pixmap. After
-creating the simple drawing program, we will extend it by adding
-support for XInput devices, such as drawing tablets. GTK provides
-support routines which makes getting extended information, such as
-pressure and tilt, from such devices quite easy.
+<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
+name="raph@acm.org"></tt>
+for hello world ala GTK, widget packing, and general all around wisdom.
+He's also generously donated a home for this tutorial.
-<!-- ----------------------------------------------------------------- -->
-<sect1> Event Handling
+<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
+name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program..
+and the ability to make it :)
-<p>
-The GTK signals we have already discussed are for high-level actions,
-such as a menu item being selected. However, sometimes it is useful to
-learn about lower-level occurrences, such as the mouse being moved, or
-a key being pressed. There are also GTK signals corresponding to these
-low-level <em>events</em>. The handlers for these signals have an
-extra parameter which is a pointer to a structure containing
-information about the event. For instance, motion events handlers are
-passed a pointer to a GdkEventMotion structure which looks (in part)
-like:
+<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
+name="werner.koch@guug.de"></tt> for converting the original plain text to
+SGML, and the widget class hierarchy.
-<tscreen><verb>
-struct _GdkEventMotion
-{
- GdkEventType type;
- GdkWindow *window;
- guint32 time;
- gdouble x;
- gdouble y;
- ...
- guint state;
- ...
-};
-</verb></tscreen>
+<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
+name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and
+the table packing tutorial.
-<tt/type/ will be set to the event type, in this case
-<tt/GDK_MOTION_NOTIFY/, window is the window in which the event
-occured. <tt/x/ and <tt/y/ give the coordinates of the event,
-and <tt/state/ specifies the modifier state when the event
-occurred (that is, it specifies which modifier keys and mouse buttons
-were pressed.) It is the bitwise OR of some of the following:
+<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
+name="owt1@cornell.edu"></tt> for the EventBox widget section (and
+the patch to the distro). He's also responsible for the selections code and
+tutorial, as well as the sections on writing your own GTK widgets, and the
+example application. Thanks a lot Owen for all you help!
-<tscreen><verb>
-GDK_SHIFT_MASK
-GDK_LOCK_MASK
-GDK_CONTROL_MASK
-GDK_MOD1_MASK
-GDK_MOD2_MASK
-GDK_MOD3_MASK
-GDK_MOD4_MASK
-GDK_MOD5_MASK
-GDK_BUTTON1_MASK
-GDK_BUTTON2_MASK
-GDK_BUTTON3_MASK
-GDK_BUTTON4_MASK
-GDK_BUTTON5_MASK
-</verb></tscreen>
+<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
+name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook,
+Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark!
+You've been a great help.
-<p>
-As for other signals, to determine what happens when an event occurs
-we call <tt>gtk_signal_connect()</tt>. But we also need let GTK
-know which events we want to be notified about. To do this, we call
-the function:
+<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
+name="timj@psynet.net"></tt> for his great job on the Lists Widget.
+Thanks Tim :)
-<tscreen><verb>
-void gtk_widget_set_events (GtkWidget *widget,
- gint events);
-</verb></tscreen>
+<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
+name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial.
-The second field specifies the events we are interested in. It
-is the bitwise OR of constants that specify different types
-of events. For future reference the event types are:
+<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
+name="johnsonm@redhat.com"></tt> for info and code for popup menus.
-<tscreen><verb>
-GDK_EXPOSURE_MASK
-GDK_POINTER_MOTION_MASK
-GDK_POINTER_MOTION_HINT_MASK
-GDK_BUTTON_MOTION_MASK
-GDK_BUTTON1_MOTION_MASK
-GDK_BUTTON2_MOTION_MASK
-GDK_BUTTON3_MOTION_MASK
-GDK_BUTTON_PRESS_MASK
-GDK_BUTTON_RELEASE_MASK
-GDK_KEY_PRESS_MASK
-GDK_KEY_RELEASE_MASK
-GDK_ENTER_NOTIFY_MASK
-GDK_LEAVE_NOTIFY_MASK
-GDK_FOCUS_CHANGE_MASK
-GDK_STRUCTURE_MASK
-GDK_PROPERTY_CHANGE_MASK
-GDK_PROXIMITY_IN_MASK
-GDK_PROXIMITY_OUT_MASK
-</verb></tscreen>
+</itemize>
+<p>
+And to all of you who commented and helped refine this document.
+<p>
+Thanks.
-There are a few subtle points that have to be observed when calling
-<tt/gtk_widget_set_events()/. First, it must be called before the X window
-for a GTK widget is created. In practical terms, this means you
-should call it immediately after creating the widget. Second, the
-widget must have an associated X window. For efficiency, many widget
-types do not have their own window, but draw in their parent's window.
-These widgets are:
+<!-- ***************************************************************** -->
+<sect> Tutorial Copyright and Permissions Notice
+<!-- ***************************************************************** -->
-<tscreen><verb>
-GtkAlignment
-GtkArrow
-GtkBin
-GtkBox
-GtkImage
-GtkItem
-GtkLabel
-GtkPixmap
-GtkScrolledWindow
-GtkSeparator
-GtkTable
-GtkAspectFrame
-GtkFrame
-GtkVBox
-GtkHBox
-GtkVSeparator
-GtkHSeparator
-</verb></tscreen>
+<p>
+The GTK Tutorial is Copyright (C) 1997 Ian Main.
-To capture events for these widgets, you need to use an EventBox
-widget. See the section on
-<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for
-details.
+Copyright (C) 1998 Tony Gale.
+<p>
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+<P>Permission is granted to copy and distribute modified versions of
+this document under the conditions for verbatim copying, provided that
+this copyright notice is included exactly as in the original,
+and that the entire resulting derived work is distributed under
+the terms of a permission notice identical to this one.
+<P>Permission is granted to copy and distribute translations of this
+document into another language, under the above conditions for modified
+versions.
+<P>If you are intending to incorporate this document into a published
+work, please contact the maintainer, and we will make an effort
+to ensure that you have the most up to date information available.
+<P>There is no guarentee that this document lives up to its intended
+purpose. This is simply provided as a free resource. As such,
+the authors and maintainers of the information provided within can
+not make any guarentee that the information is even accurate.
-<p>
-For our drawing program, we want to know when the mouse button is
-pressed and when the mouse is moved, so we specify
-<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also
-want to know when we need to redraw our window, so we specify
-<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a
-Configure event when our window size changes, we don't have to specify
-the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is
-automatically specified for all windows.
+<!-- ***************************************************************** -->
+<appendix>
+<!-- ***************************************************************** -->
+<!-- ***************************************************************** -->
+<sect> Code Examples
+<!-- ***************************************************************** -->
<p>
-It turns out, however, that there is a problem with just specifying
-<tt/GDK_POINTER_MOTION_MASK/. This will cause the server to add a new
-motion event to the event queue every time the user moves the mouse.
-Imagine that it takes us 0.1 seconds to handle a motion event, but the
-X server queues a new motion event every 0.05 seconds. We will soon
-get way behind the users drawing. If the user draws for 5 seconds,
-it will take us another 5 seconds to catch up after they release
-the mouse button! What we would like is to only get one motion
-event for each event we process. The way to do this is to
-specify <tt/GDK_POINTER_MOTION_HINT_MASK/.
+Below are the code examples that are used in the above text
+which are not included in complete form elsewhere.
+<!-- ----------------------------------------------------------------- -->
+<sect1> Scribble
<p>
-When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, the server sends
-us a motion event the first time the pointer moves after entering
-our window, or after a button press or release event. Subsequent
-motion events will be suppressed until we explicitely ask for
-the position of the pointer using the function:
-
<tscreen><verb>
-GdkWindow* gdk_window_get_pointer (GdkWindow *window,
- gint *x,
- gint *y,
- GdkModifierType *mask);
-</verb></tscreen>
+/* example-start scribble-simple scribble-simple.c */
-(There is another function, <tt>gtk_widget_get_pointer()</tt> which
-has a simpler interface, but turns out not to be very useful, since
-it only retrieves the position of the mouse, not whether the buttons
-are pressed.)
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
-<p>
-The code to set the events for our window then looks like:
+#include <gtk/gtk.h>
-<tscreen><verb>
- gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
- (GtkSignalFunc) expose_event, NULL);
- gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
- (GtkSignalFunc) configure_event, NULL);
- gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
- (GtkSignalFunc) motion_notify_event, NULL);
- gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
- (GtkSignalFunc) button_press_event, NULL);
+/* Backing pixmap for drawing area */
+static GdkPixmap *pixmap = NULL;
- gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
- | GDK_LEAVE_NOTIFY_MASK
- | GDK_BUTTON_PRESS_MASK
- | GDK_POINTER_MOTION_MASK
- | GDK_POINTER_MOTION_HINT_MASK);
-</verb></tscreen>
+/* Create a new backing pixmap of the appropriate size */
+static gint
+configure_event (GtkWidget *widget, GdkEventConfigure *event)
+{
+ if (pixmap)
+ gdk_pixmap_unref(pixmap);
-We'll save the "expose_event" and "configure_event" handlers for
-later. The "motion_notify_event" and "button_press_event" handlers
-pretty simple:
+ pixmap = gdk_pixmap_new(widget->window,
+ widget->allocation.width,
+ widget->allocation.height,
+ -1);
+ gdk_draw_rectangle (pixmap,
+ widget->style->white_gc,
+ TRUE,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+
+ return TRUE;
+}
+
+/* Redraw the screen from the backing pixmap */
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *event)
+{
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+
+ return FALSE;
+}
+
+/* Draw a rectangle on the screen */
+static void
+draw_brush (GtkWidget *widget, gdouble x, gdouble y)
+{
+ GdkRectangle update_rect;
+
+ update_rect.x = x - 5;
+ update_rect.y = y - 5;
+ update_rect.width = 10;
+ update_rect.height = 10;
+ gdk_draw_rectangle (pixmap,
+ widget->style->black_gc,
+ TRUE,
+ update_rect.x, update_rect.y,
+ update_rect.width, update_rect.height);
+ gtk_widget_draw (widget, &update_rect);
+}
-<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && pixmap != NULL)
- draw_brush (widget, event->x, event->y);
+ draw_brush (widget, event->x, event->y);
return TRUE;
}
return TRUE;
}
+
+void
+quit ()
+{
+ gtk_exit (0);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *drawing_area;
+ GtkWidget *vbox;
+
+ GtkWidget *button;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_name (window, "Test Input");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+ gtk_widget_show (vbox);
+
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC (quit), NULL);
+
+ /* Create the drawing area */
+
+ drawing_area = gtk_drawing_area_new ();
+ gtk_drawing_area_size (GTK_DRAWING_AREA (drawing_area), 200, 200);
+ gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
+
+ gtk_widget_show (drawing_area);
+
+ /* Signals used to handle backing pixmap */
+
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
+ (GtkSignalFunc) expose_event, NULL);
+ gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
+ (GtkSignalFunc) configure_event, NULL);
+
+ /* Event signals */
+
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
+ (GtkSignalFunc) motion_notify_event, NULL);
+ gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
+ (GtkSignalFunc) button_press_event, NULL);
+
+ gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK);
+
+ /* .. And a quit button */
+ button = gtk_button_new_with_label ("Quit");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (gtk_widget_destroy),
+ GTK_OBJECT (window));
+ gtk_widget_show (button);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
</verb></tscreen>
<!-- ----------------------------------------------------------------- -->
-<sect1> The DrawingArea Widget, And Drawing
+<sect1> GtkDial
+<!-- ----------------------------------------------------------------- -->
+<sect2> gtkdial.h
<p>
-We know turn to the process of drawing on the screen. The
-widget we use for this is the DrawingArea widget. A drawing area
-widget is essentially an X window and nothing more. It is a blank
-canvas in which we can draw whatever we like. A drawing area
-is created using the call:
-
<tscreen><verb>
-GtkWidget* gtk_drawing_area_new (void);
-</verb></tscreen>
+/* example-start gtkdial gtkdial.h */
-A default size for the widget can be specified by calling:
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GTK_DIAL_H__
+#define __GTK_DIAL_H__
-<tscreen><verb>
-void gtk_drawing_area_size (GtkDrawingArea *darea,
- gint width,
- gint height);
-</verb></tscreen>
-This default size can be overriden, as is true for all widgets,
-by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can
-be overridden if the user manually resizes the the window containing
-the drawing area.
+#include <gdk/gdk.h>
+#include <gtk/gtkadjustment.h>
+#include <gtk/gtkwidget.h>
-<p>
-It should be noted that when we create a DrawingArea widget, we are,
-<em>completely</em> responsible for drawing the contents. If our
-window is obscured then uncovered, we get an exposure event and must
-redraw what was previously hidden.
-<p>
-Having to remember everything that was drawn on the screen so we
-can properly redraw it can, to say the least, be a nuisance. In
-addition, it can be visually distracting if portions of the
-window are cleared, then redrawn step by step. The solution to
-this problem is to use an offscreen <em>backing pixmap</em>.
-Instead of drawing directly to the screen, we draw to an image
-stored in server memory but not displayed, then when the image
-changes or new portions of the image are displayed, we copy the
-relevant portions onto the screen.
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
+#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
+#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
+
+
+typedef struct _GtkDial GtkDial;
+typedef struct _GtkDialClass GtkDialClass;
+
+struct _GtkDial
+{
+ GtkWidget widget;
+
+ /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
+ guint policy : 2;
+
+ /* Button currently pressed or 0 if none */
+ guint8 button;
-<p>
-To create an offscreen pixmap, we call the function:
+ /* Dimensions of dial components */
+ gint radius;
+ gint pointer_width;
-<tscreen><verb>
-GdkPixmap* gdk_pixmap_new (GdkWindow *window,
- gint width,
- gint height,
- gint depth);
-</verb></tscreen>
+ /* ID of update timer, or 0 if none */
+ guint32 timer;
-The <tt>window</tt> parameter specifies a GDK window that this pixmap
-takes some of its properties from. <tt>width</tt> and <tt>height</tt>
-specify the size of the pixmap. <tt>depth</tt> specifies the <em>color
-depth</em>, that is the number of bits per pixel, for the new window.
-If the depth is specified as <tt>-1</tt>, it will match the depth
-of <tt>window</tt>.
+ /* Current angle */
+ gfloat angle;
-<p>
-We create the pixmap in our "configure_event" handler. This event
-is generated whenever the window changes size, including when it
-is originally created.
+ /* Old values from adjustment stored so we know when something changes */
+ gfloat old_value;
+ gfloat old_lower;
+ gfloat old_upper;
-<tscreen><verb>
-/* Backing pixmap for drawing area */
-static GdkPixmap *pixmap = NULL;
+ /* The adjustment object that stores the data for this dial */
+ GtkAdjustment *adjustment;
+};
-/* Create a new backing pixmap of the appropriate size */
-static gint
-configure_event (GtkWidget *widget, GdkEventConfigure *event)
+struct _GtkDialClass
{
- if (pixmap)
- {
- gdk_pixmap_destroy(pixmap);
- }
- pixmap = gdk_pixmap_new(widget->window,
- widget->allocation.width,
- widget->allocation.height,
- -1);
- gdk_draw_rectangle (pixmap,
- widget->style->white_gc,
- TRUE,
- 0, 0,
- widget->allocation.width,
- widget->allocation.height);
+ GtkWidgetClass parent_class;
+};
- return TRUE;
-}
-</verb></tscreen>
-The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap
-initially to white. We'll say more about that in a moment.
+GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
+guint gtk_dial_get_type (void);
+GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
+void gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy);
-<p>
-Our exposure event handler then simply copies the relevant portion
-of the pixmap onto the screen (we determine the area we need
-to redraw by using the event->area field of the exposure event):
+void gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
-<tscreen><verb>
-/* Refill the screen from the backing pixmap */
-static gint
-expose_event (GtkWidget *widget, GdkEventExpose *event)
-{
- gdk_draw_pixmap(widget->window,
- widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
- pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- return FALSE;
-}
+#endif /* __GTK_DIAL_H__ */
+/* example-end */
</verb></tscreen>
-We've now seen how to keep the screen up to date with our pixmap, but
-how do we actually draw interesting stuff on our pixmap? There are a
-large number of calls in GTK's GDK library for drawing on
-<em>drawables</em>. A drawable is simply something that can be drawn
-upon. It can be a window, a pixmap, or a bitmap (a black and white
-image). We've already seen two such calls above,
-<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The
-complete list is:
-
+<!-- ----------------------------------------------------------------- -->
+<sect2> gtkdial.c
+<p>
<tscreen><verb>
-gdk_draw_line ()
-gdk_draw_rectangle ()
-gdk_draw_arc ()
-gdk_draw_polygon ()
-gdk_draw_string ()
-gdk_draw_text ()
-gdk_draw_pixmap ()
-gdk_draw_bitmap ()
-gdk_draw_image ()
-gdk_draw_points ()
-gdk_draw_segments ()
-</verb></tscreen>
+/* example-start gtkdial gtkdial.c */
-See the reference documentation or the header file
-<tt><gdk/gdk.h></tt> for further details on these functions.
-These functions all share the same first two arguments. The first
-argument is the drawable to draw upon, the second argument is a
-<em>graphics context</em> (GC).
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include <math.h>
+#include <stdio.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
-<p>
-A graphics context encapsulates information about things such as
-foreground and background color and line width. GDK has a full set of
-functions for creating and modifying graphics contexts, but to keep
-things simple we'll just use predefined graphics contexts. Each widget
-has an associated style. (Which can be modified in a gtkrc file, see
-the section GTK's rc file.) This, among other things, stores a number
-of graphics contexts. Some examples of accessing these graphics
-contexts are:
+#include "gtkdial.h"
-<tscreen><verb>
-widget->style->white_gc
-widget->style->black_gc
-widget->style->fg_gc[GTK_STATE_NORMAL]
-widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
-</verb></tscreen>
+#define SCROLL_DELAY_LENGTH 300
+#define DIAL_DEFAULT_SIZE 100
-The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and
-<tt>light_gc</tt> are indexed by a parameter of type
-<tt>GtkStateType</tt> which can take on the values:
+/* Forward declararations */
-<tscreen><verb>
-GTK_STATE_NORMAL,
-GTK_STATE_ACTIVE,
-GTK_STATE_PRELIGHT,
-GTK_STATE_SELECTED,
-GTK_STATE_INSENSITIVE
-</verb></tscreen>
+static void gtk_dial_class_init (GtkDialClass *klass);
+static void gtk_dial_init (GtkDial *dial);
+static void gtk_dial_destroy (GtkObject *object);
+static void gtk_dial_realize (GtkWidget *widget);
+static void gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition);
+static void gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gint gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event);
+static gint gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+static gint gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event);
+static gint gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event);
+static gint gtk_dial_timer (GtkDial *dial);
+
+static void gtk_dial_update_mouse (GtkDial *dial, gint x, gint y);
+static void gtk_dial_update (GtkDial *dial);
+static void gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data);
+static void gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data);
-For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground
-color is white and the default background color, dark blue.
+/* Local data */
-<p>
-Our function <tt>draw_brush()</tt>, which does the actual drawing
-on the screen, is then:
+static GtkWidgetClass *parent_class = NULL;
-<tscreen><verb>
-/* Draw a rectangle on the screen */
-static void
-draw_brush (GtkWidget *widget, gdouble x, gdouble y)
+guint
+gtk_dial_get_type ()
{
- GdkRectangle update_rect;
+ static guint dial_type = 0;
- update_rect.x = x - 5;
- update_rect.y = y - 5;
- update_rect.width = 10;
- update_rect.height = 10;
- gdk_draw_rectangle (pixmap,
- widget->style->black_gc,
- TRUE,
- update_rect.x, update_rect.y,
- update_rect.width, update_rect.height);
- gtk_widget_draw (widget, &update_rect);
-}
-</verb></tscreen>
+ if (!dial_type)
+ {
+ GtkTypeInfo dial_info =
+ {
+ "GtkDial",
+ sizeof (GtkDial),
+ sizeof (GtkDialClass),
+ (GtkClassInitFunc) gtk_dial_class_init,
+ (GtkObjectInitFunc) gtk_dial_init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL,
+ };
-After we draw the rectangle representing the brush onto the pixmap,
-we call the function:
+ dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
+ }
-<tscreen><verb>
-void gtk_widget_draw (GtkWidget *widget,
- GdkRectangle *area);
-</verb></tscreen>
+ return dial_type;
+}
-which notifies X that the area given by the <tt>area</tt> parameter
-needs to be updated. X will eventually generate an expose event
-(possibly combining the areas passed in several calls to
-<tt>gtk_widget_draw()</tt>) which will cause our expose event handler
-to copy the relevant portions to the screen.
+static void
+gtk_dial_class_init (GtkDialClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
-<p>
-We have now covered the entire drawing program except for a few
-mundane details like creating the main window. The complete
-source code is available from the location from which you got
-this tutorial, or from:
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+ parent_class = gtk_type_class (gtk_widget_get_type ());
+ object_class->destroy = gtk_dial_destroy;
-<!-- ----------------------------------------------------------------- -->
-<sect1> Adding XInput support
+ widget_class->realize = gtk_dial_realize;
+ widget_class->expose_event = gtk_dial_expose;
+ widget_class->size_request = gtk_dial_size_request;
+ widget_class->size_allocate = gtk_dial_size_allocate;
+ widget_class->button_press_event = gtk_dial_button_press;
+ widget_class->button_release_event = gtk_dial_button_release;
+ widget_class->motion_notify_event = gtk_dial_motion_notify;
+}
+
+static void
+gtk_dial_init (GtkDial *dial)
+{
+ dial->button = 0;
+ dial->policy = GTK_UPDATE_CONTINUOUS;
+ dial->timer = 0;
+ dial->radius = 0;
+ dial->pointer_width = 0;
+ dial->angle = 0.0;
+ dial->old_value = 0.0;
+ dial->old_lower = 0.0;
+ dial->old_upper = 0.0;
+ dial->adjustment = NULL;
+}
-<p>
+GtkWidget*
+gtk_dial_new (GtkAdjustment *adjustment)
+{
+ GtkDial *dial;
-It is now possible to buy quite inexpensive input devices such
-as drawing tablets, which allow drawing with a much greater
-ease of artistic expression than does a mouse. The simplest way
-to use such devices is simply as a replacement for the mouse,
-but that misses out many of the advantages of these devices,
-such as:
+ dial = gtk_type_new (gtk_dial_get_type ());
-<itemize>
-<item> Pressure sensitivity
-<item> Tilt reporting
-<item> Sub-pixel positioning
-<item> Multiple inputs (for example, a stylus with a point and eraser)
-</itemize>
+ if (!adjustment)
+ adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-For information about the XInput extension, see the <htmlurl
-url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
-name="XInput-HOWTO">.
+ gtk_dial_set_adjustment (dial, adjustment);
-<p>
-If we examine the full definition of, for example, the GdkEventMotion
-structure, we see that it has fields to support extended device
-information.
+ return GTK_WIDGET (dial);
+}
-<tscreen><verb>
-struct _GdkEventMotion
+static void
+gtk_dial_destroy (GtkObject *object)
{
- GdkEventType type;
- GdkWindow *window;
- guint32 time;
- gdouble x;
- gdouble y;
- gdouble pressure;
- gdouble xtilt;
- gdouble ytilt;
- guint state;
- gint16 is_hint;
- GdkInputSource source;
- guint32 deviceid;
-};
-</verb></tscreen>
-
-<tt/pressure/ gives the pressure as a floating point number between
-0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between
--1 and 1, corresponding to the degree of tilt in each direction.
-<tt/source/ and <tt/deviceid/ specify the device for which the
-event occurred in two different ways. <tt/source/ gives some simple
-information about the type of device. It can take the enumeration
-values.
-
-<tscreen><verb>
-GDK_SOURCE_MOUSE
-GDK_SOURCE_PEN
-GDK_SOURCE_ERASER
-GDK_SOURCE_CURSOR
-</verb></tscreen>
+ GtkDial *dial;
-<tt/deviceid/ specifies a unique numeric ID for the device. This can
-be used to find out further information about the device using the
-<tt/gdk_input_list_devices()/ call (see below). The special value
-<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually
-the mouse.)
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_DIAL (object));
-<sect2> Enabling extended device information
+ dial = GTK_DIAL (object);
-<p>
-To let GTK know about our interest in the extended device information,
-we merely have to add a single line to our program:
+ if (dial->adjustment)
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
-<tscreen><verb>
-gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
-</verb></tscreen>
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
-By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that
-we are interested in extension events, but only if we don't have
-to draw our own cursor. See the section <ref
-id="sec_Further_Sophistications" name="Further Sophistications"> below
-for more information about drawing the cursor. We could also
-give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing
-to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert
-back to the default condition.
+GtkAdjustment*
+gtk_dial_get_adjustment (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
-<p>
-This is not completely the end of the story however. By default,
-no extension devices are enabled. We need a mechanism to allow
-users to enable and configure their extension devices. GTK provides
-the InputDialog widget to automate this process. The following
-procedure manages an InputDialog widget. It creates the dialog if
-it isn't present, and raises it to the top otherwise.
+ return dial->adjustment;
+}
-<tscreen><verb>
void
-input_dialog_destroy (GtkWidget *w, gpointer data)
+gtk_dial_set_update_policy (GtkDial *dial,
+ GtkUpdateType policy)
{
- *((GtkWidget **)data) = NULL;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
+
+ dial->policy = policy;
}
void
-create_input_dialog ()
+gtk_dial_set_adjustment (GtkDial *dial,
+ GtkAdjustment *adjustment)
{
- static GtkWidget *inputd = NULL;
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
- if (!inputd)
+ if (dial->adjustment)
{
- inputd = gtk_input_dialog_new();
+ gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
+ gtk_object_unref (GTK_OBJECT (dial->adjustment));
+ }
- gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
- (GtkSignalFunc)input_dialog_destroy, &inputd);
- gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
- "clicked",
- (GtkSignalFunc)gtk_widget_hide,
- GTK_OBJECT(inputd));
- gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
+ dial->adjustment = adjustment;
+ gtk_object_ref (GTK_OBJECT (dial->adjustment));
- gtk_widget_show (inputd);
- }
- else
- {
- if (!GTK_WIDGET_MAPPED(inputd))
- gtk_widget_show(inputd);
- else
- gdk_window_raise(inputd->window);
- }
+ gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
+ (GtkSignalFunc) gtk_dial_adjustment_changed,
+ (gpointer) dial);
+ gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
+ (GtkSignalFunc) gtk_dial_adjustment_value_changed,
+ (gpointer) dial);
+
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+
+ gtk_dial_update (dial);
}
-</verb></tscreen>
-(You might want to take note of the way we handle this dialog. By
-connecting to the "destroy" signal, we make sure that we don't keep a
-pointer to dialog around after it is destroyed - that could lead to a
-segfault.)
+static void
+gtk_dial_realize (GtkWidget *widget)
+{
+ GtkDial *dial;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
-<p>
-The InputDialog has two buttons "Close" and "Save", which by default
-have no actions assigned to them. In the above function we make
-"Close" hide the dialog, hide the "Save" button, since we don't
-implement saving of XInput options in this program.
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
-<sect2> Using extended device information
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+ dial = GTK_DIAL (widget);
-<p>
-Once we've enabled the device, we can just use the extended
-device information in the extra fields of the event structures.
-In fact, it is always safe to use this information since these
-fields will have reasonable default values even when extended
-events are not enabled.
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
-<p>
-Once change we do have to make is to call
-<tt/gdk_input_window_get_pointer()/ instead of
-<tt/gdk_window_get_pointer/. This is necessary because
-<tt/gdk_window_get_pointer/ doesn't return the extended device
-information.
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+ widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
-<tscreen><verb>
-void gdk_input_window_get_pointer (GdkWindow *window,
- guint32 deviceid,
- gdouble *x,
- gdouble *y,
- gdouble *pressure,
- gdouble *xtilt,
- gdouble *ytilt,
- GdkModifierType *mask);
-</verb></tscreen>
+ widget->style = gtk_style_attach (widget->style, widget->window);
-When calling this function, we need to specify the device ID as
-well as the window. Usually, we'll get the device ID from the
-<tt/deviceid/ field of an event structure. Again, this function
-will return reasonable values when extension events are not
-enabled. (In this case, <tt/event->deviceid/ will have the value
-<tt/GDK_CORE_POINTER/).
+ gdk_window_set_user_data (widget->window, widget);
-So the basic structure of our button-press and motion event handlers,
-doesn't change much - we just need to add code to deal with the
-extended information.
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
+}
+
+static void
+gtk_dial_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = DIAL_DEFAULT_SIZE;
+ requisition->height = DIAL_DEFAULT_SIZE;
+}
+
+static void
+gtk_dial_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkDial *dial;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_DIAL (widget));
+ g_return_if_fail (allocation != NULL);
+
+ widget->allocation = *allocation;
+ dial = GTK_DIAL (widget);
+
+ if (GTK_WIDGET_REALIZED (widget))
+ {
+
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ }
+ dial->radius = MIN(allocation->width,allocation->height) * 0.45;
+ dial->pointer_width = dial->radius / 5;
+}
-<tscreen><verb>
static gint
-button_press_event (GtkWidget *widget, GdkEventButton *event)
+gtk_dial_expose (GtkWidget *widget,
+ GdkEventExpose *event)
{
- print_button_press (event->deviceid);
+ GtkDial *dial;
+ GdkPoint points[3];
+ gdouble s,c;
+ gdouble theta;
+ gint xc, yc;
+ gint tick_length;
+ gint i;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ if (event->count > 0)
+ return FALSE;
- if (event->button == 1 && pixmap != NULL)
- draw_brush (widget, event->source, event->x, event->y, event->pressure);
+ dial = GTK_DIAL (widget);
- return TRUE;
-}
+ gdk_window_clear_area (widget->window,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
-static gint
-motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
-{
- gdouble x, y;
- gdouble pressure;
- GdkModifierType state;
+ xc = widget->allocation.width/2;
+ yc = widget->allocation.height/2;
- if (event->is_hint)
- gdk_input_window_get_pointer (event->window, event->deviceid,
- &x, &y, &pressure, NULL, NULL, &state);
- else
+ /* Draw ticks */
+
+ for (i=0; i<25; i++)
{
- x = event->x;
- y = event->y;
- pressure = event->pressure;
- state = event->state;
+ theta = (i*M_PI/18. - M_PI/6.);
+ s = sin(theta);
+ c = cos(theta);
+
+ tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
+
+ gdk_draw_line (widget->window,
+ widget->style->fg_gc[widget->state],
+ xc + c*(dial->radius - tick_length),
+ yc - s*(dial->radius - tick_length),
+ xc + c*dial->radius,
+ yc - s*dial->radius);
}
-
- if (state & GDK_BUTTON1_MASK && pixmap != NULL)
- draw_brush (widget, event->source, x, y, pressure);
+
+ /* Draw pointer */
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+
+ points[0].x = xc + s*dial->pointer_width/2;
+ points[0].y = yc + c*dial->pointer_width/2;
+ points[1].x = xc + c*dial->radius;
+ points[1].y = yc - s*dial->radius;
+ points[2].x = xc - s*dial->pointer_width/2;
+ points[2].y = yc - c*dial->pointer_width/2;
+
+ gtk_draw_polygon (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ points, 3,
+ TRUE);
- return TRUE;
+ return FALSE;
}
-</verb></tscreen>
-
-We also need to do something with the new information. Our new
-<tt/draw_brush()/ function draws with a different color for
-each <tt/event->source/ and changes the brush size depending
-on the pressure.
-<tscreen><verb>
-/* Draw a rectangle on the screen, size depending on pressure,
- and color on the type of device */
-static void
-draw_brush (GtkWidget *widget, GdkInputSource source,
- gdouble x, gdouble y, gdouble pressure)
+static gint
+gtk_dial_button_press (GtkWidget *widget,
+ GdkEventButton *event)
{
- GdkGC *gc;
- GdkRectangle update_rect;
+ GtkDial *dial;
+ gint dx, dy;
+ double s, c;
+ double d_parallel;
+ double d_perpendicular;
- switch (source)
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ dial = GTK_DIAL (widget);
+
+ /* Determine if button press was within pointer region - we
+ do this by computing the parallel and perpendicular distance of
+ the point where the mouse was pressed from the line passing through
+ the pointer */
+
+ dx = event->x - widget->allocation.width / 2;
+ dy = widget->allocation.height / 2 - event->y;
+
+ s = sin(dial->angle);
+ c = cos(dial->angle);
+
+ d_parallel = s*dy + c*dx;
+ d_perpendicular = fabs(s*dx - c*dy);
+
+ if (!dial->button &&
+ (d_perpendicular < dial->pointer_width/2) &&
+ (d_parallel > - dial->pointer_width))
{
- case GDK_SOURCE_MOUSE:
- gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
- break;
- case GDK_SOURCE_PEN:
- gc = widget->style->black_gc;
- break;
- case GDK_SOURCE_ERASER:
- gc = widget->style->white_gc;
- break;
- default:
- gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
+ gtk_grab_add (widget);
+
+ dial->button = event->button;
+
+ gtk_dial_update_mouse (dial, event->x, event->y);
}
- update_rect.x = x - 10 * pressure;
- update_rect.y = y - 10 * pressure;
- update_rect.width = 20 * pressure;
- update_rect.height = 20 * pressure;
- gdk_draw_rectangle (pixmap, gc, TRUE,
- update_rect.x, update_rect.y,
- update_rect.width, update_rect.height);
- gtk_widget_draw (widget, &update_rect);
+ return FALSE;
}
-</verb></tscreen>
-<sect2> Finding out more about a device
+static gint
+gtk_dial_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkDial *dial;
-<p>
-As an example of how to find out more about a device, our program
-will print the name of the device that generates each button
-press. To find out the name of a device, we call the function:
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
-<tscreen><verb>
-GList *gdk_input_list_devices (void);
-</verb></tscreen>
+ dial = GTK_DIAL (widget);
-which returns a GList (a linked list type from the glib library)
-of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined
-as:
+ if (dial->button == event->button)
+ {
+ gtk_grab_remove (widget);
-<tscreen><verb>
-struct _GdkDeviceInfo
-{
- guint32 deviceid;
- gchar *name;
- GdkInputSource source;
- GdkInputMode mode;
- gint has_cursor;
- gint num_axes;
- GdkAxisUse *axes;
- gint num_keys;
- GdkDeviceKey *keys;
-};
-</verb></tscreen>
+ dial->button = 0;
-Most of these fields are configuration information that you
-can ignore unless you are implemented XInput configuration
-saving. The we are interested in here is <tt/name/ which is
-simply the name that X assigns to the device. The other field
-that isn't configuration information is <tt/has_cursor/. If
-<tt/has_cursor/ is false, then we we need to draw our own
-cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/,
-we don't have to worry about this.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_timeout_remove (dial->timer);
+
+ if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
+ (dial->old_value != dial->adjustment->value))
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
-<p>
-Our <tt/print_button_press()/ function simply iterates through
-the returned list until it finds a match, then prints out
-the name of the device.
+ return FALSE;
+}
-<tscreen><verb>
-static void
-print_button_press (guint32 deviceid)
+static gint
+gtk_dial_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
{
- GList *tmp_list;
+ GtkDial *dial;
+ GdkModifierType mods;
+ gint x, y, mask;
- /* gdk_input_list_devices returns an internal list, so we shouldn't
- free it afterwards */
- tmp_list = gdk_input_list_devices();
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
- while (tmp_list)
+ dial = GTK_DIAL (widget);
+
+ if (dial->button != 0)
{
- GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
+ x = event->x;
+ y = event->y;
- if (info->deviceid == deviceid)
+ if (event->is_hint || (event->window != widget->window))
+ gdk_window_get_pointer (widget->window, &x, &y, &mods);
+
+ switch (dial->button)
{
- printf("Button press on device '%s'\n", info->name);
- return;
+ case 1:
+ mask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ mask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ mask = GDK_BUTTON3_MASK;
+ break;
+ default:
+ mask = 0;
+ break;
}
- tmp_list = tmp_list->next;
+ if (mods & mask)
+ gtk_dial_update_mouse (dial, x,y);
}
+
+ return FALSE;
}
-</verb></tscreen>
-That completes the changes to ``XInputize'' our program. As with
-the first version, the complete source is available at the location
-from which you got this tutorial, or from:
+static gint
+gtk_dial_timer (GtkDial *dial)
+{
+ g_return_val_if_fail (dial != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
-<htmlurl url="http://www.gtk.org/~otaylor/gtk/tutorial/"
-name="http://www.gtk.org/~otaylor/gtk/tutorial/">
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ return FALSE;
+}
-<sect2> Further sophistications <label id="sec_Further_Sophistications">
+static void
+gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
+{
+ gint xc, yc;
+ gfloat old_value;
-<p>
-Although our program now supports XInput quite well, it lacks some
-features we would want in a full-featured application. First, the user
-probably doesn't want to have to configure their device each time they
-run the program, so we should allow them to save the device
-configuration. This is done by iterating through the return of
-<tt/gdk_input_list_devices()/ and writing out the configuration to a
-file.
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-<p>
-To restore the state next time the program is run, GDK provides
-functions to change device configuration:
+ xc = GTK_WIDGET(dial)->allocation.width / 2;
+ yc = GTK_WIDGET(dial)->allocation.height / 2;
-<tscreen><verb>
-gdk_input_set_extension_events()
-gdk_input_set_source()
-gdk_input_set_mode()
-gdk_input_set_axes()
-gdk_input_set_key()
-</verb></tscreen>
+ old_value = dial->adjustment->value;
+ dial->angle = atan2(yc-y, x-xc);
-(The list returned from <tt/gdk_input_list_devices()/ should not be
-modified directly.) An example of doing this can be found in the
-drawing program gsumi. (Available from <htmlurl
-url="http://www.msc.cornell.edu/~otaylor/gsumi/"
-name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it
-would be nice to have a standard way of doing this for all
-applications. This probably belongs at a slightly higher level than
-GTK, perhaps in the GNOME library.
+ if (dial->angle < -M_PI/2.)
+ dial->angle += 2*M_PI;
-<p>
-Another major ommission that we have mentioned above is the lack of
-cursor drawing. Platforms other than XFree86 currently do not allow
-simultaneously using a device as both the core pointer and directly by
-an application. See the <url
-url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
-name="XInput-HOWTO"> for more information about this. This means that
-applications that want to support the widest audience need to draw
-their own cursor.
+ if (dial->angle < -M_PI/6)
+ dial->angle = -M_PI/6;
-<p>
-An application that draws it's own cursor needs to do two things:
-determine if the current device needs a cursor drawn or not, and
-determine if the current device is in proximity. (If the current
-device is a drawing tablet, it's a nice touch to make the cursor
-disappear when the stylus is lifted from the tablet. When the
-device is touching the stylus, that is called "in proximity.")
-The first is done by searching the device list, as we did
-to find out the device name. The second is achieved by selecting
-"proximity_out" events. An example of drawing one's own cursor is
-found in the 'testinput' program found in the GTK distribution.
+ if (dial->angle > 7.*M_PI/6.)
+ dial->angle = 7.*M_PI/6.;
-<!-- ***************************************************************** -->
-<sect>Tips For Writing GTK Applications
-<!-- ***************************************************************** -->
+ dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
+ (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
-<p>
-This section is simply a gathering of wisdom, general style guidelines and hints to
-creating good GTK applications. It is totally useless right now cause it's
-only a topic sentence :)
+ if (dial->adjustment->value != old_value)
+ {
+ if (dial->policy == GTK_UPDATE_CONTINUOUS)
+ {
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
+ else
+ {
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
-Use GNU autoconf and automake! They are your friends :) I am planning to
-make a quick intro on them here.
+ if (dial->policy == GTK_UPDATE_DELAYED)
+ {
+ if (dial->timer)
+ gtk_timeout_remove (dial->timer);
-<!-- ***************************************************************** -->
-<sect>Contributing
-<!-- ***************************************************************** -->
+ dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
+ (GtkFunction) gtk_dial_timer,
+ (gpointer) dial);
+ }
+ }
+ }
+}
-<p>
-This document, like so much other great software out there, was created for
-free by volunteers. If you are at all knowledgeable about any aspect of GTK
-that does not already have documentation, please consider contributing to
-this document.
-<p>
-If you do decide to contribute, please mail your text to Tony Gale,
-<tt><htmlurl url="mailto:gale@gtk.org"
-name="gale@gtk.org"></tt>. Also, be aware that the entirety of this
-document is free, and any addition by yourself must also be free. That is,
-people may use any portion of your examples in their programs, and copies
-of this document may be distributed at will etc.
-<p>
-Thank you.
+static void
+gtk_dial_update (GtkDial *dial)
+{
+ gfloat new_value;
+
+ g_return_if_fail (dial != NULL);
+ g_return_if_fail (GTK_IS_DIAL (dial));
-<!-- ***************************************************************** -->
-<sect>Credits
-<!-- ***************************************************************** -->
-<p>
-I would like to thank the following for their contributions to this text.
+ new_value = dial->adjustment->value;
+
+ if (new_value < dial->adjustment->lower)
+ new_value = dial->adjustment->lower;
-<itemize>
-<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
-name="chamele0n@geocities.com"></tt> for the menus tutorial.
+ if (new_value > dial->adjustment->upper)
+ new_value = dial->adjustment->upper;
-<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
-name="raph@acm.org"></tt>
-for hello world ala GTK, widget packing, and general all around wisdom.
-He's also generously donated a home for this tutorial.
+ if (new_value != dial->adjustment->value)
+ {
+ dial->adjustment->value = new_value;
+ gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
+ }
-<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
-name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program..
-and the ability to make it :)
+ dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
+ (dial->adjustment->upper - dial->adjustment->lower);
-<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
-name="werner.koch@guug.de"></tt> for converting the original plain text to
-SGML, and the widget class hierarchy.
+ gtk_widget_draw (GTK_WIDGET(dial), NULL);
+}
-<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
-name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and
-the table packing tutorial.
+static void
+gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
-name="owt1@cornell.edu"></tt> for the EventBox widget section (and
-the patch to the distro). He's also responsible for the selections code and
-tutorial, as well as the sections on writing your own GTK widgets, and the
-example application. Thanks a lot Owen for all you help!
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
-<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
-name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook,
-Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark!
-You've been a great help.
+ dial = GTK_DIAL (data);
-<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
-name="timj@psynet.net"></tt> for his great job on the Lists Widget.
-Thanks Tim :)
+ if ((dial->old_value != adjustment->value) ||
+ (dial->old_lower != adjustment->lower) ||
+ (dial->old_upper != adjustment->upper))
+ {
+ gtk_dial_update (dial);
-<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
-name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial.
+ dial->old_value = adjustment->value;
+ dial->old_lower = adjustment->lower;
+ dial->old_upper = adjustment->upper;
+ }
+}
-<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
-name="johnsonm@redhat.com"></tt> for info and code for popup menus.
+static void
+gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ GtkDial *dial;
-</itemize>
-<p>
-And to all of you who commented and helped refine this document.
-<p>
-Thanks.
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
-<!-- ***************************************************************** -->
-<sect> Tutorial Copyright and Permissions Notice
-<!-- ***************************************************************** -->
+ dial = GTK_DIAL (data);
-<p>
-The GTK Tutorial is Copyright (C) 1997 Ian Main.
+ if (dial->old_value != adjustment->value)
+ {
+ gtk_dial_update (dial);
-Copyright (C) 1998 Tony Gale.
-<p>
-Permission is granted to make and distribute verbatim copies of this
-manual provided the copyright notice and this permission notice are
-preserved on all copies.
-<P>Permission is granted to copy and distribute modified versions of
-this document under the conditions for verbatim copying, provided that
-this copyright notice is included exactly as in the original,
-and that the entire resulting derived work is distributed under
-the terms of a permission notice identical to this one.
-<P>Permission is granted to copy and distribute translations of this
-document into another language, under the above conditions for modified
-versions.
-<P>If you are intending to incorporate this document into a published
-work, please contact the maintainer, and we will make an effort
-to ensure that you have the most up to date information available.
-<P>There is no guarentee that this document lives up to its intended
-purpose. This is simply provided as a free resource. As such,
-the authors and maintainers of the information provided within can
-not make any guarentee that the information is even accurate.
+ dial->old_value = adjustment->value;
+ }
+}
+/* example-end */
+</verb></tscreen>
</article>
--- /dev/null
+
+CC = gcc
+
+text: text.c
+ $(CC) `gtk-config --cflags` `gtk-config --libs` text.c -o text
+
+clean:
+ rm -f *.o text
--- /dev/null
+/* example-start text text.c */
+
+/* text.c */
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+
+void text_toggle_editable (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_editable(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void text_toggle_word_wrap (GtkWidget *checkbutton,
+ GtkWidget *text)
+{
+ gtk_text_set_word_wrap(GTK_TEXT(text),
+ GTK_TOGGLE_BUTTON(checkbutton)->active);
+}
+
+void close_application( GtkWidget *widget, gpointer data )
+{
+ gtk_main_quit();
+}
+
+int main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *box1;
+ GtkWidget *box2;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *check;
+ GtkWidget *separator;
+ GtkWidget *table;
+ GtkWidget *vscrollbar;
+ GtkWidget *text;
+ GdkColormap *cmap;
+ GdkColor colour;
+ GdkFont *fixed_font;
+
+ FILE *infile;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_usize (window, 600, 500);
+ gtk_window_set_policy (GTK_WINDOW(window), TRUE, TRUE, FALSE);
+ gtk_signal_connect (GTK_OBJECT (window), "destroy",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_window_set_title (GTK_WINDOW (window), "Text Widget Example");
+ gtk_container_border_width (GTK_CONTAINER (window), 0);
+
+
+ box1 = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), box1);
+ gtk_widget_show (box1);
+
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
+ gtk_widget_show (box2);
+
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2);
+ gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2);
+ gtk_box_pack_start (GTK_BOX (box2), table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ /* Create the GtkText widget */
+ text = gtk_text_new (NULL, NULL);
+ gtk_text_set_editable (GTK_TEXT (text), TRUE);
+ gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (text);
+
+ /* Add a vertical scrollbar to the GtkText widget */
+ vscrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
+ gtk_table_attach (GTK_TABLE (table), vscrollbar, 1, 2, 0, 1,
+ GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+ gtk_widget_show (vscrollbar);
+
+ /* Get the system colour map and allocate the colour red */
+ cmap = gdk_colormap_get_system();
+ colour.red = 0xffff;
+ colour.green = 0;
+ colour.blue = 0;
+ if (!gdk_color_alloc(cmap, &colour)) {
+ g_error("couldn't allocate colour");
+ }
+
+ /* Load a fixed font */
+ fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*");
+
+ /* Realizing a widget creates a window for it, ready for us to insert some text */
+ gtk_widget_realize (text);
+
+ /* Freeze the text widget, ready for multiple updates */
+ gtk_text_freeze (GTK_TEXT (text));
+
+ /* Insert some coloured text */
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "Supports ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &colour, NULL,
+ "colored ", -1);
+ gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
+ "text and different ", -1);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, &text->style->black, NULL,
+ "fonts\n\n", -1);
+
+ /* Load the file text.c into the text window */
+
+ infile = fopen("text.c", "r");
+
+ if (infile) {
+ char buffer[1024];
+ int nchars;
+
+ while (1)
+ {
+ nchars = fread(buffer, 1, 1024, infile);
+ gtk_text_insert (GTK_TEXT (text), fixed_font, NULL,
+ NULL, buffer, nchars);
+
+ if (nchars < 1024)
+ break;
+ }
+
+ fclose (infile);
+ }
+
+ /* Thaw the text widget, allowing the updates to become visible */
+ gtk_text_thaw (GTK_TEXT (text));
+
+ hbox = gtk_hbutton_box_new ();
+ gtk_box_pack_start (GTK_BOX (box2), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ check = gtk_check_button_new_with_label("Editable");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, FALSE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_editable), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
+ gtk_widget_show (check);
+ check = gtk_check_button_new_with_label("Wrap Words");
+ gtk_box_pack_start (GTK_BOX (hbox), check, FALSE, TRUE, 0);
+ gtk_signal_connect (GTK_OBJECT(check), "toggled",
+ GTK_SIGNAL_FUNC(text_toggle_word_wrap), text);
+ gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), FALSE);
+ gtk_widget_show (check);
+
+ separator = gtk_hseparator_new ();
+ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0);
+ gtk_widget_show (separator);
+
+ box2 = gtk_vbox_new (FALSE, 10);
+ gtk_container_border_width (GTK_CONTAINER (box2), 10);
+ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0);
+ gtk_widget_show (box2);
+
+ button = gtk_button_new_with_label ("close");
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC(close_application),
+ NULL);
+ gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_widget_grab_default (button);
+ gtk_widget_show (button);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+/* example-end */
--- /dev/null
+
+CC = gcc
+
+tree: tree.c
+ $(CC) `gtk-config --cflags` `gtk-config --libs` tree.c -o tree
+
+clean:
+ rm -f *.o tree
--- /dev/null
+/* example-start tree tree.c */
+
+#include <gtk/gtk.h>
+
+/* for all the GtkItem:: and GtkTreeItem:: signals */
+static void cb_itemsignal (GtkWidget *item, gchar *signame)
+{
+ gchar *name;
+ GtkLabel *label;
+
+ /* It's a GtkBin, so it has one child, which we know to be a
+ label, so get that */
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ /* Get the text of the label */
+ gtk_label_get (label, &name);
+ /* Get the level of the tree which the item is in */
+ g_print ("%s called for item %s->%p, level %d\n", signame, name,
+ item, GTK_TREE (item->parent)->level);
+}
+
+/* Note that this is never called */
+static void cb_unselect_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
+{
+ g_print ("unselect_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
+}
+
+/* Note that this is called every time the user clicks on an item,
+ whether it is already selected or not. */
+static void cb_select_child (GtkWidget *root_tree, GtkWidget *child,
+ GtkWidget *subtree)
+{
+ g_print ("select_child called for root tree %p, subtree %p, child %p\n",
+ root_tree, subtree, child);
+}
+
+static void cb_selection_changed (GtkWidget *tree)
+{
+ GList *i;
+
+ g_print ("selection_change called for tree %p\n", tree);
+ g_print ("selected objects are:\n");
+
+ i = GTK_TREE_SELECTION(tree);
+ while (i){
+ gchar *name;
+ GtkLabel *label;
+ GtkWidget *item;
+
+ /* Get a GtkWidget pointer from the list node */
+ item = GTK_WIDGET (i->data);
+ label = GTK_LABEL (GTK_BIN (item)->child);
+ gtk_label_get (label, &name);
+ g_print ("\t%s on level %d\n", name, GTK_TREE
+ (item->parent)->level);
+ i = i->next;
+ }
+}
+
+int main (int argc, char *argv[])
+{
+ GtkWidget *window, *scrolled_win, *tree;
+ static gchar *itemnames[] = {"Foo", "Bar", "Baz", "Quux",
+ "Maurice"};
+ gint i;
+
+ gtk_init (&argc, &argv);
+
+ /* a generic toplevel window */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect (GTK_OBJECT(window), "delete_event",
+ GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
+ gtk_container_border_width (GTK_CONTAINER(window), 5);
+
+ /* A generic scrolled window */
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_usize (scrolled_win, 150, 200);
+ gtk_container_add (GTK_CONTAINER(window), scrolled_win);
+ gtk_widget_show (scrolled_win);
+
+ /* Create the root tree */
+ tree = gtk_tree_new();
+ g_print ("root tree is %p\n", tree);
+ /* connect all GtkTree:: signals */
+ gtk_signal_connect (GTK_OBJECT(tree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), tree);
+ gtk_signal_connect (GTK_OBJECT(tree), "selection_changed",
+ GTK_SIGNAL_FUNC(cb_selection_changed), tree);
+ /* Add it to the scrolled window */
+ gtk_container_add (GTK_CONTAINER(scrolled_win), tree);
+ /* Set the selection mode */
+ gtk_tree_set_selection_mode (GTK_TREE(tree),
+ GTK_SELECTION_MULTIPLE);
+ /* Show it */
+ gtk_widget_show (tree);
+
+ for (i = 0; i < 5; i++){
+ GtkWidget *subtree, *item;
+ gint j;
+
+ /* Create a tree item */
+ item = gtk_tree_item_new_with_label (itemnames[i]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(item), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(item), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(item), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(item), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(item), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ /* Add it to the parent tree */
+ gtk_tree_append (GTK_TREE(tree), item);
+ /* Show it - this can be done at any time */
+ gtk_widget_show (item);
+ /* Create this item's subtree */
+ subtree = gtk_tree_new();
+ g_print ("-> item %s->%p, subtree %p\n", itemnames[i], item,
+ subtree);
+
+ /* This is still necesary if you want these signals to be called
+ for the subtree's children. Note that selection_change will be
+ signalled for the root tree regardless. */
+ gtk_signal_connect (GTK_OBJECT(subtree), "select_child",
+ GTK_SIGNAL_FUNC(cb_select_child), subtree);
+ gtk_signal_connect (GTK_OBJECT(subtree), "unselect_child",
+ GTK_SIGNAL_FUNC(cb_unselect_child), subtree);
+ /* This has absolutely no effect, because it is completely ignored
+ in subtrees */
+ gtk_tree_set_selection_mode (GTK_TREE(subtree),
+ GTK_SELECTION_SINGLE);
+ /* Neither does this, but for a rather different reason - the
+ view_mode and view_line values of a tree are propagated to
+ subtrees when they are mapped. So, setting it later on would
+ actually have a (somewhat unpredictable) effect */
+ gtk_tree_set_view_mode (GTK_TREE(subtree), GTK_TREE_VIEW_ITEM);
+ /* Set this item's subtree - note that you cannot do this until
+ AFTER the item has been added to its parent tree! */
+ gtk_tree_item_set_subtree (GTK_TREE_ITEM(item), subtree);
+
+ for (j = 0; j < 5; j++){
+ GtkWidget *subitem;
+
+ /* Create a subtree item, in much the same way */
+ subitem = gtk_tree_item_new_with_label (itemnames[j]);
+ /* Connect all GtkItem:: and GtkTreeItem:: signals */
+ gtk_signal_connect (GTK_OBJECT(subitem), "select",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "select");
+ gtk_signal_connect (GTK_OBJECT(subitem), "deselect",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "deselect");
+ gtk_signal_connect (GTK_OBJECT(subitem), "toggle",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "toggle");
+ gtk_signal_connect (GTK_OBJECT(subitem), "expand",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "expand");
+ gtk_signal_connect (GTK_OBJECT(subitem), "collapse",
+ GTK_SIGNAL_FUNC(cb_itemsignal), "collapse");
+ g_print ("-> -> item %s->%p\n", itemnames[j], subitem);
+ /* Add it to its parent tree */
+ gtk_tree_append (GTK_TREE(subtree), subitem);
+ /* Show it */
+ gtk_widget_show (subitem);
+ }
+ }
+
+ /* Show the window and loop endlessly */
+ gtk_widget_show (window);
+ gtk_main();
+ return 0;
+}
+/* example-end */